Using Field Visualizations

Visual Lansa’s field visualization capabilities are very powerful. I have come across many areas in code where the visual display of data is manipulated in code within forms and reusable parts…only to be duplicated in other forms and reusable parts. Inevitably, this leads to bugs as different areas of an application behave differently (never mind the terrible inefficiency). In Lansa you can create a reusable part to act as a visual class for a field or multiple fields. In the example below, the original form contained code to display a field “prompt” to indicate the data required without taking up space with a label. The amount of code just to manage the prompt text was extraneous and redundant. This was refactored into a single reusable part and added to each fields visualization as defined below. One could easily take this code a step further and add formatting and validation routines for common data types like currency, phone numbers, email addresses, etc. If you are writing Visual Lansa applications in RDMLX and are not using field visualizations I would highly recommend looking into their use!

Screenshot

Field Visualization Reusable Part Code:

FUNCTION OPTIONS(*DIRECT)
BEGIN_COM ROLE(*EXTENDS #PRIM_PANL) DEFAULTPTY(Value) DISPLAYPOSITION(1) HEIGHT(18) LAYOUTMANAGER(#ATLM_1) LEFT(0) TABPOSITION(1) TABSTOP(False) TOP(0) WIDTH(200)

* =======================================================
* Component Definitions
* =======================================================
DEFINE_COM CLASS(#PRIM_ALPH) NAME(#PasswordChar)
DEFINE_COM CLASS(#PRIM_BOLN) NAME(#RequiredField)
DEFINE_COM CLASS(#PRIM_EDIT) NAME(#EditBox) DISPLAYPOSITION(1) HEIGHT(18) LEFT(0) PARENT(#COM_OWNER) SHOWSELECTION(False) SHOWSELECTIONHILIGHT(False) TABPOSITION(1) TOP(0) WIDTH(200)
DEFINE_COM CLASS(#PRIM_ATLI) NAME(#ATLI_1) ATTACHMENT(Center) MANAGE(#EditBox) PARENT(#ATLM_1)
DEFINE_COM CLASS(#PRIM_ATLM) NAME(#ATLM_1)

* =======================================================
* Field Definitions
* =======================================================
DEFINE FIELD(#wNme) TYPE(*CHAR) LENGTH(10)
DEFINE FIELD(#wRet) TYPE(*CHAR) LENGTH(2)
DEFINE FIELD(#wTyp) TYPE(*CHAR) LENGTH(1)
DEFINE FIELD(#wLen) TYPE(*DEC) LENGTH(15) DECIMALS(0)
DEFINE FIELD(#wDec) TYPE(*DEC) LENGTH(15) DECIMALS(0)
DEFINE FIELD(#wChr) TYPE(*DEC) LENGTH(15) DECIMALS(0)
DEFINE FIELD(#wRef) TYPE(*CHAR) LENGTH(10)
DEFINE FIELD(#wDsc) TYPE(*CHAR) LENGTH(40)
DEFINE FIELD(#wLbl) TYPE(*CHAR) LENGTH(15)
DEFINE FIELD(#wHdg) TYPE(*CHAR) LENGTH(60)
DEFINE FIELD(#wOut) TYPE(*CHAR) LENGTH(40)
DEFINE FIELD(#wInp) TYPE(*CHAR) LENGTH(40)

* =======================================================
* Property Definitions
* =======================================================
DEFINE_PTY NAME(Value) GET(GetValue) SET(SetValue)
DEFINE_PTY NAME(ReadOnly) GET(GetReadOnly) SET(SetReadOnly)
DEFINE_PTY NAME(ShowError) GET(GetShowError) SET(SetShowError)
DEFINE_PTY NAME(PasswordChar) GET(*AUTO #PasswordChar) SET(*AUTO #PasswordChar)
DEFINE_PTY NAME(Required) GET(*AUTO #RequiredField) SET(*AUTO #RequiredField)

* =======================================================
* Event Definitions
* =======================================================
DEFINE_EVT NAME(Changed)
DEFINE_EVT NAME(GotFocus)
DEFINE_EVT NAME(LostFocus)
DEFINE_EVT NAME(KeyPress)
DEFINE_MAP FOR(*OUTPUT) CLASS(#STD_FLAG) NAME(#KeyCode)
DEFINE_MAP FOR(*OUTPUT) CLASS(#STD_FLAG) NAME(#Char)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#IsAltDown)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#IsControlDown)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#IsShiftDown)
DEFINE_MAP FOR(*BOTH) CLASS(#PRIM_BOLN) NAME(#Handled)

* =======================================================
* Property Routines
* =======================================================
PTYROUTINE NAME(SetValue)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_VAR) NAME(#Value)
#EditBox.Value := #Value
#COM_OWNER.SetFieldPrompt
ENDROUTINE

PTYROUTINE NAME(GetValue)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_VAR) NAME(#Value)
IF (#EditBox.Value <> #wDsc)
#Value := #EditBox.Value
ENDIF
ENDROUTINE

PTYROUTINE NAME(SetReadOnly)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#ReadOnly)
#EditBox.ReadOnly := #ReadOnly
#EditBox.AutoSelect := #ReadOnly.Not
#COM_OWNER.SetFieldPrompt
ENDROUTINE

PTYROUTINE NAME(GetReadOnly)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#ReadOnly)
#ReadOnly := #EditBox.ReadOnly
ENDROUTINE

PTYROUTINE NAME(SetShowError)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#ShowError)
#EditBox.ShowError := #ShowError
#COM_OWNER.SetFieldPrompt
ENDROUTINE

PTYROUTINE NAME(GetShowError)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#ShowError)
#ShowError := #EditBox.ShowError
ENDROUTINE

* =======================================================
* Event Routines
* =======================================================
EVTROUTINE HANDLING(#COM_OWNER.CreateInstance) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)
SET COM(#COM_OWNER) VISUALSTYLE(#VSSITAGRY)
#wNme := #COM_OWNER.ComponentPatternName
USE BUILTIN(GET_FIELD) WITH_ARGS(#wNme) TO_GET(#wRet #wTyp #wLen #wDec #wRef #wDsc #wLbl #wHdg #wOut #wInp)
IF (#COM_SELF.ComponentTag <> ”)
#wDsc := #COM_SELF.ComponentTag
ENDIF
ENDROUTINE

EVTROUTINE HANDLING(#EditBox.KeyPress) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS) KEYCODE(#KeyCode) CHAR(#Character) HANDLED(#Handled) ISALTDOWN(#AltDown) ISSHIFTDOWN(#ShiftDown) ISCONTROLDOWN(#ControlDown)
DEFINE_COM CLASS(#STD_FLAG) NAME(#KeyedCharacter)
IF ((*Not #Handled) *And (*Not #EditBox.ReadOnly))
IF (#KeyCode = IsChar)
#KeyedCharacter := #Character
IF (*Not #wInp.Uppercase.Contains( ‘LC’ ))
#KeyedCharacter := #KeyedCharacter.Uppercase
ENDIF
#Handled := True
IF (#EditBox.Value = #wDsc)
#wChr := #EditBox.SelectionEnd
#EditBox.Value := #KeyedCharacter
ELSE
IF (#EditBox.SelectionEnd <> #EditBox.SelectionStart)
IF (#EditBox.SelectionEnd > #EditBox.SelectionStart)
#wChr := #EditBox.SelectionStart
#EditBox.Value := #EditBox.Value.Replace( #EditBox.Value.Substring( #EditBox.SelectionStart (#EditBox.SelectionEnd – #EditBox.SelectionStart) ) #KeyedCharacter )
ELSE
#wChr := #EditBox.SelectionEnd
#EditBox.Value := #EditBox.Value.Replace( #EditBox.Value.Substring( #EditBox.SelectionEnd (#EditBox.SelectionStart – #EditBox.SelectionEnd) ) #KeyedCharacter )
ENDIF
ELSE
#wChr := #EditBox.SelectionEnd
#EditBox.Value := #EditBox.Value.InsertString( #KeyedCharacter #EditBox.SelectionEnd )
ENDIF
ENDIF
#EditBox.SelectionStart #EditBox.SelectionEnd := #wChr + 1
#COM_OWNER.SetFieldPrompt
SIGNAL EVENT(Changed)
ENDIF
ENDIF
SIGNAL EVENT(KeyPress) KEYCODE(#KeyCode) CHAR(#KeyedCharacter) ISALTDOWN(#AltDown) ISCONTROLDOWN(#ControlDown) ISSHIFTDOWN(#ShiftDown) HANDLED(#Handled)
ENDROUTINE

EVTROUTINE HANDLING(#EditBox.Changed) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)
#COM_OWNER.SetFieldPrompt
SIGNAL EVENT(Changed)
ENDROUTINE

EVTROUTINE HANDLING(#EditBox.GotFocus) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)
SIGNAL EVENT(GotFocus)
ENDROUTINE

EVTROUTINE HANDLING(#EditBox.LostFocus) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)
SIGNAL EVENT(LostFocus)
ENDROUTINE

* =======================================================
* Method Routines
* =======================================================
MTHROUTINE NAME(SetFieldPrompt)
IF (#EditBox.Value.CurChars > 0)
IF (#EditBox.Value = #wDsc)
#EditBox.PasswordChar := ”
IF (#RequiredField)
#EditBox.VisualStyle <= #VSSITARED
ELSE
#EditBox.VisualStyle <= #VSSITAGRY
ENDIF
ELSE
#EditBox.PasswordChar := #PasswordChar
#EditBox.VisualStyle <= #VSSNRMBLK
ENDIF
ELSE
#EditBox.PasswordChar := ”
#EditBox.Value := #wDsc
#EditBox.VisualStyle <= #VSSITAGRY
ENDIF
ENDROUTINE

END_COM

Field Visualization Definition Code:

BEGIN_COM ROLE(*EXTENDS #PRIM_OBJT)
BEGIN_COM ROLE(*Visual #PRIM_EVEF) NAME(#VisualEdit) DEFAULTVISUAL(True) HEIGHT(18) MARGINLEFT(100) USEPICKLIST(False) WIDTH(200)
END_COM
BEGIN_COM ROLE(*Visual_Part #OBJFLDPRM) NAME(#VisualPrompt)
END_COM
END_COM

Normal Visual Style Code:

BEGIN_COM ROLE(*EXTENDS #PRIM_VS) DEFAULT(#SCHEME)
DEFINE_COM CLASS(#PRIM_VSS) NAME(#SCHEME) CAPTIONS(#CAPTION) TITLES(#CAPTION) VALUES(#VALUE)
DEFINE_COM CLASS(#PRIM_VSI) NAME(#CAPTION) FACENAME(‘Tahoma’) FONTSIZE(8) NORMBACKCOLOR(ButtonFace) TEXTCOLOR(Black)
DEFINE_COM CLASS(#PRIM_VSI) NAME(#VALUE) ALTERNBACKCOLOR(Window) BORDERSTYLE(3DLeft) FACENAME(‘Tahoma’) FONTSIZE(8) TEXTCOLOR(Black)
END_COM

Italic Gray Visual Style Code:

BEGIN_COM ROLE(*EXTENDS #PRIM_VS) DEFAULT(#SCHEME)
DEFINE_COM CLASS(#PRIM_VSS) NAME(#SCHEME) CAPTIONS(#CAPTION) TITLES(#CAPTION) VALUES(#VALUE)
DEFINE_COM CLASS(#PRIM_VSI) NAME(#CAPTION) FACENAME(‘Tahoma’) FONTSIZE(8) NORMBACKCOLOR(ButtonFace) TEXTCOLOR(Black)
DEFINE_COM CLASS(#PRIM_VSI) NAME(#VALUE) ALTERNBACKCOLOR(Window) BORDERSTYLE(3DLeft) FACENAME(‘Tahoma’) FONTSIZE(8) ITALIC(True) TEXTCOLOR(Gray)
END_COM

Italic Red Visual Style Code:

BEGIN_COM ROLE(*EXTENDS #PRIM_VS) DEFAULT(#SCHEME)
DEFINE_COM CLASS(#PRIM_VSS) NAME(#SCHEME) CAPTIONS(#CAPTION) TITLES(#CAPTION) VALUES(#VALUE)
DEFINE_COM CLASS(#PRIM_VSI) NAME(#CAPTION) FACENAME(‘Tahoma’) FONTSIZE(8) NORMBACKCOLOR(ButtonFace) TEXTCOLOR(Black)
DEFINE_COM CLASS(#PRIM_VSI) NAME(#VALUE) ALTERNBACKCOLOR(Window) BORDERSTYLE(3DLeft) FACENAME(‘Tahoma’) FONTSIZE(8) ITALIC(True) TEXTCOLOR(Red)
END_COM

Running Lansa as a Windows Service

I have seen the question of how to run a Lansa function as a Windows Service posted may times in various online forums. The Lansa executable (X_RUN.exe) does not have the entry and exit points for the Windows Service Controller. There are third-party application available for running an executable as a program, but they are not lightweight and they cost money. I solved this problem by creating a simple C# solution in Visual Studio 2010.

The Generic Windows Service Solution creates a process object with configurable command line arguments. In order to run a Lansa function as a service the command line is specified as the path to X_RUN.exe in the system executable folder and the command line arguments are the X_RUN arguments. The Lansa function can execute in a loop with a brief pause using the OV_SLEEP BIF. The Lansa function should not loop without a delay as the executing function will consume increasing system memory without giving the operating system the opportunity to reclaim resources. The service can be configured to either terminate when the process exits or restart the process if it is terminated.

The Generic Windows Service and/or Visual Studio 2010 Solution can be downloaded from the files section of the Yahoo Lansa User Group at http://tech.groups.yahoo.com/group/lansa2/files/. You must be a member of the group to download from the files section. The Windows Service is in the GenericService.zip file and includes batch files for installing and configuring the service.

Below is the C# source  code for the Generic Windows Service solution:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using System.Configuration;

namespace LansaService
{
public partial class LansaService : ServiceBase
{

Process LansaProc = new Process();
Timer LansaTimer = new Timer();
bool LansaRestart = true;

public LansaService()
{
InitializeComponent();
}

protected override void OnStart(string[] args)
{

LansaRestart = Convert.ToBoolean( ConfigurationSettings.AppSettings[“Restart”].ToString());
startLansa();

}

private bool startLansa()
{

LansaTimer.Stop();
LansaProc.StartInfo.WorkingDirectory = ConfigurationSettings.AppSettings[“WorkingDirectory”].ToString();
LansaProc.StartInfo.FileName = ConfigurationSettings.AppSettings[“ExecutablePath”].ToString();
LansaProc.StartInfo.Arguments = ConfigurationSettings.AppSettings[“Arguments”].ToString();
LansaProc.StartInfo.UseShellExecute = false;
LansaProc.StartInfo.RedirectStandardOutput = false;
LansaProc.StartInfo.RedirectStandardError = false;

if (LansaProc.Start())
{
LansaTimer.Interval = 5000;
LansaTimer.Elapsed += new ElapsedEventHandler(LansaTimer_Elapsed);
LansaTimer.Start();
return true;
}
else
{
OnStop();
return false;
}

}

void LansaTimer_Elapsed(object sender, ElapsedEventArgs e)
{

if (LansaProc.HasExited)
{
if (LansaRestart)
{
startLansa();
}
else
{
OnStop();
}

}

}

protected override void OnStop()
{
try
{
LansaProc.Kill();
}
catch (Exception)
{
}
}
}
}

Understanding Component Scope

In Visual Lansa the concept of  component scope is often misunderstood. I have come across many component definitions that misuse the scope parameter. Often, it is because definitions are copied between Owner Components that do not function the same way and analysis is not done to evaluate the proper component scope.

A defined component in Visual Lansa may be given different levels of Scope within the application; “The SCOPE parameter is used to create component instances that are shared between different instances of component owners” (LANSA User Assistance V11 SP5, Technical Reference Guide 8.4.1). The SCOPE parameter can be specified as *INSTANCE, *LOCAL, *DEFAULT, *SHARED, or *APPLICATION.

*INSTANCE – When a component is defined with parameter SCOPE(*INSTANCE) a new instance of the component is created for each owner component

*LOCAL – When a component is defined with parameter SCOPE(*LOCAL) a new instance of the component is created every time an Event, Method, or Property Routine is executed; as such, this value is only valid when the component is defined immediately after a EVTROUTINE, MTHROUTINE, or PTYROUTINE command.

*DEFAULT – When a component is defined with parameter SCOPE(*DEFAULT) the value is determined from the position of the component definition. Components defined immediately after the BEGIN_COM command are interpreted as *INSTANCE, components defined immediately after EVTROUTINE, MTHROUTINE, or PTYROUTINE commands are interpreted as *LOCAL.

*SHARED – When a component is defined with parameter SCOPE(*SHARED) only one instance of the component is created for all owner components of the same class. Owner components of different classes do not share component instances defined as scope *SHARED.

*APPLICATION – When a component is defined with parameter SCOPE(*APPLICATION) only one instance of the component is created for all owner classes within the application, as such all DLL’s required to implement the defined component and owner component will remain in memory for the lifetime of the component instance and possibly the lifetime of the application.

The following is a visual illustration of different scoping parameters demonstrated with a visual component. The same principal applies for non-visual components. In these examples a parent form and child form(s) of different classes dynamically create instances of a calendar control.

SCOPE(*INSTANCE)

Instance

All components reference their own instance of the calendar control. As such, when a child component closes the reference to the calendar control is set to null and its DestroyInstance event is signaled. This allows the child forms handle count to reach zero at which point its own DestroyInstance event is signaled. The DLLs for both the calendar control and form will be unloaded during garbage cleanup.

SCOPE(*SHARED)

Shared

When the calendar control is defined as SCOPE(*SHARED) a new instance of the component is created for each owner component class; because Parent Form and Child Form are separate classes they do not share an instance of the calendar control. Notice that only one Child Form has a visible calendar control, because the calendar control is shared between the two like classes it can only be visible on one…because the calendar control can only have one owner component. This is not an issue for non-visual components but further illustrates the point that there is only one shared instance. In this case the calendar control will not be destroyed until its reference is set to *Null by both Child Forms, the DLLS for the child forms and calendar control will also not be unloaded until both references are set to *Null and both child forms are closed.

SCOPE(*APPLICATION)

Application
When the calendar control is defined as SCOPE(*APPLICATION) a single instance of the component is created for all components within the application (that are defined as SCOPE(*APPLICATION). The single component instance is shared regardless of owner component class. Owner components containing components defined as SCOPE(*APPLICATION) will not be destroyed (and DLLS will not be unloaded) until all references of the Defined Component within the application are set to *Null.

The first question to ask when determining the proper component scope is ‘Is the component used in multiple owner components?’ – If No, then the Defined Component should be SCOPE(*INSTANCE) when defined after a BEGIN_COM or SCOPE(*LOCAL) when defined within an event, property, or method routine…this is the default component scope.

If a component is used in multiple owner owner components ask ‘Are the Owner Components main features of the application that will exist as long as the application is running?’ – If No, then the Defined Component should be SCOPE(*INSTANCE) when defined after a BEGIN_COM or SCOPE(*LOCAL) when defined within an event, property, or method routine…this is the default component scope.

If the Owner Components are main application features that should share an instance of the Defined Component ask ‘Are the Owner Components of the same component class?’ – If Yes, then the Defined Component should be SCOPE(*SHARED); If No, the Defined Component should be SCOPE(*APPLICATION).

A great deal of thought must be given to the Scope parameter when defining components within a large application. Allowing components within an application to share instances of a component provides improved performance by avoiding multiple CreateInstance  routines, but it also may prevent DLLs from being unloaded and system resources being freed.

Poll Results: Lansa Version 12



Below are the reults for the recently posted poll regarding new features in Lansa Version 12:

Poll Results

I think that it is interesting that new SQL Database features head the list. I think this reflects the fact that even through all of the recent developments in presenting user’s with richer content and sophisticated interfaces Lansa developers are still constantly seeking the most efficient ways to transform data into information and present it to the end-user in a meaningful way.

Multi-Calendar Component

A recent post on the Yahoo Lansa Group asked about a date picker with a two month display. While there was not an easy way of using the Calendar primitive I had previously created a dynamic calendar control for other purposes. I was able to modify it slightly to allow for multiple months to be displayed and also allow for multiple dates to be selected.

The component illustrated below can be configures to display one to three calendars in an horizontal or vertical row. It can also be configured to select and return a single date or a date range where start and end dates are returned. Other normal calendar properties are configurable such as whether to show the current date and the first day of the week.

This calendar component also uses panels and buttons which will alter the display to match the current Lansa theme (#SYS_APPLN.Theme).

Multi-Calendar Component

The required components can be downloaded in zip format using the link below:

Click to Download Calendar Component

Poll: Lansa Version 12

A Microsoft Word Style Rich Text Memo Box

A Microsoft Word style Rich Text Memo Box has many possible uses within applications. When I first created this component it was to be used in a product specifications system where text instructions could be accompanied by diagrams. This component renders the rich-text in HTML using a lightweight ActiveX control from nBit information technologies (http://nbit.net.au/vpages.aspx?ID=HTML%20Editor%20OCX). In my original implementation the markup could then be converted to PDF for email or web delivery. There could be many additional uses for such a component: an editor for email templates as part of a automated mailing system, a way to add user-defined content into a WAM (CMS), etc .   

 

FUNCTION OPTIONS(*DIRECT)
BEGIN_COM ROLE(*EXTENDS #PRIM_PANL) Displayposition(1) Height(786) Layoutmanager(#ATLM_1) Left(0) Tabposition(1) Top(0) Width(938)

*  =======================================================
*  Component Definitions
*  =======================================================
DEFINE_COM CLASS(#STD_MEMO) NAME(#WorkText)
DEFINE_COM CLASS(#PRIM_ATLM) NAME(#ATLM_1)
DEFINE_COM CLASS(#VA_HTMLED.HTMLed) NAME(#VA_HTMLED) Displayposition(1) Height(769) Left(-3) Parent(#PANL_BACK) Tabposition(1) Top(-3) Visualstyle(#VS_WHITE9) Width(944)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#PANL_TOOLS) Displayposition(1) Height(24) Layoutmanager(#FWLM_1) Left(0) Parent(#PANL_TOP) Tabposition(1) Tabstop(False) Themestyle(Themed) Top(0) Visualstyleofparent(False) Width(807)
DEFINE_COM CLASS(#PRIM_ATLI) NAME(#ATLI_2) Attachment(Center) Manage(#PANL_TOOLS) Parent(#ATLM_1)
DEFINE_COM CLASS(#PRIM_ATLI) NAME(#ATLI_3) Attachment(Center) Manage(#VA_HTMLED) Marginbottom(-3) Marginleft(-3) Marginright(-3) Margintop(-3) Parent(#ATLM_1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#PANL_BACK) Displayposition(1) Height(763) Layoutmanager(#ATLM_1) Left(0) Parent(#COM_OWNER) Tabposition(1) Tabstop(False) Top(23) Visualstyle(#VS_WHITE6) Width(938)
DEFINE_COM CLASS(#PRIM_ATLI) NAME(#ATLI_4) Attachment(Center) Manage(#PANL_BACK) Margintop(-1) Parent(#ATLM_1)
DEFINE_COM CLASS(#PRIM_FWLM) NAME(#FWLM_1) Flowoperation(Center) Flowoperationver(Center) Marginbottom(2) Marginleft(4) Spacing(2) Spacingitems(2)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BPASTE) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(1) Height(20) Hint(‘Paste (CTRL + V)’) Image(#Q_PST) Left(4) Parent(#PANL_TOOLS) Tabposition(1) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_1) Manage(#BPASTE) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BCOPY) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(2) Height(20) Hint(‘Copy (CTRL + C)’) Image(#Q_CPY) Left(26) Parent(#PANL_TOOLS) Tabposition(2) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_2) Manage(#BCOPY) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BCUT) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(3) Height(20) Hint(‘Cut (CTRL + X)’) Image(#Q_CUT) Left(48) Parent(#PANL_TOOLS) Tabposition(3) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_3) Manage(#BCUT) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BBOLD) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(5) Height(20) Hint(‘Bold Selection (CTRL + B)’) Image(#Q_BLD) Left(73) Parent(#PANL_TOOLS) Tabposition(4) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_4) Manage(#BBOLD) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BITALIC) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(6) Height(20) Hint(‘Italic Selection (CTRL + I)’) Image(#Q_ITA) Left(95) Parent(#PANL_TOOLS) Tabposition(5) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_5) Manage(#BITALIC) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BUNDERLINE) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(7) Height(20) Hint(‘Underline Selection (CTRL + U)’) Image(#Q_UND) Left(117) Parent(#PANL_TOOLS) Tabposition(6) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_6) Manage(#BUNDERLINE) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BSTRIKE) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(8) Height(20) Hint(‘Strikethrough Selection’) Image(#Q_STR) Left(139) Parent(#PANL_TOOLS) Tabposition(7) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_7) Manage(#BSTRIKE) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BFONT) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(10) Height(20) Hint(‘Custom Font’) Image(#Q_FNT) Left(164) Parent(#PANL_TOOLS) Tabposition(8) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_8) Manage(#BFONT) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BLEFT) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(12) Height(20) Hint(‘Left Justify’) Image(#Q_LFT) Left(189) Parent(#PANL_TOOLS) Tabposition(9) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_9) Manage(#BLEFT) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BCENTER) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(13) Height(20) Hint(‘Center Justify’) Image(#Q_CNT) Left(211) Parent(#PANL_TOOLS) Tabposition(10) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_10) Manage(#BCENTER) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BRIGHT) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(14) Height(20) Hint(‘Right Justify’) Image(#Q_RGT) Left(233) Parent(#PANL_TOOLS) Tabposition(11) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_11) Manage(#BRIGHT) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BINDENT) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(16) Height(20) Hint(‘Indent’) Image(#Q_IND) Left(258) Parent(#PANL_TOOLS) Tabposition(12) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_12) Manage(#BINDENT) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BOUTDENT) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(17) Height(20) Hint(‘Outdent’) Image(#Q_OUT) Left(280) Parent(#PANL_TOOLS) Tabposition(13) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_13) Manage(#BOUTDENT) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BORDERED) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(19) Height(20) Hint(‘Numbered List’) Image(#Q_ORD) Left(305) Parent(#PANL_TOOLS) Tabposition(14) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_14) Manage(#BORDERED) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BUNORDERED) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(20) Height(20) Hint(‘Bullet List’) Image(#Q_UNO) Left(327) Parent(#PANL_TOOLS) Tabposition(15) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_15) Manage(#BUNORDERED) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BIMAGE) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(22) Height(20) Hint(‘Insert Image’) Image(#Q_IMG) Left(352) Parent(#PANL_TOOLS) Tabposition(16) Top(1) Visible(False) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_16) Manage(#BIMAGE) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BUNDO) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(26) Height(20) Hint(‘Undo (CTRL + Z)’) Image(#Q_UDO) Left(421) Parent(#PANL_TOOLS) Tabposition(19) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_17) Manage(#BUNDO) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BREDO) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(27) Height(20) Hint(‘Redo (CTRL + Y)’) Image(#Q_RDO) Left(443) Parent(#PANL_TOOLS) Tabposition(20) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_18) Manage(#BREDO) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BSPELL) Autosize(False) Buttonstyle(FlatButton) Componentversion(1) Displayposition(29) Height(20) Hint(‘Check Spelling’) Image(#Q_SPL) Left(468) Parent(#PANL_TOOLS) Tabposition(21) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_19) Manage(#BSPELL) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_APPL.ICommonDialogFileOpen) NAME(#OpenDialog) REFERENCE(*DYNAMIC)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BLINK) Buttonstyle(FlatButton) Componentversion(1) Displayposition(23) Height(20) Hint(‘Insert Hyperlink’) Image(#Q_LNK) Left(374) Parent(#PANL_TOOLS) Tabposition(17) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_27) Manage(#BLINK) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_SPBN) NAME(#BTABLE) Buttonstyle(FlatButton) Componentversion(1) Displayposition(24) Height(20) Hint(‘Create Table’) Image(#Q_TBL) Left(396) Parent(#PANL_TOOLS) Tabposition(18) Top(1) Width(20)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_28) Manage(#BTABLE) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_1) Displayposition(4) Height(20) Left(70) Parent(#PANL_TOOLS) Tabposition(29) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_2) Displayposition(11) Height(20) Left(186) Parent(#PANL_TOOLS) Tabposition(27) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_3) Displayposition(9) Height(20) Left(161) Parent(#PANL_TOOLS) Tabposition(28) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_4) Displayposition(15) Height(20) Left(255) Parent(#PANL_TOOLS) Tabposition(26) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_5) Displayposition(18) Height(20) Left(302) Parent(#PANL_TOOLS) Tabposition(25) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_6) Displayposition(21) Height(20) Left(349) Parent(#PANL_TOOLS) Tabposition(24) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_7) Displayposition(25) Height(20) Left(418) Parent(#PANL_TOOLS) Tabposition(23) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#SEP_8) Displayposition(28) Height(20) Left(465) Parent(#PANL_TOOLS) Tabposition(22) Tabstop(False) Themedrawstyle(LightTitle) Top(1) Width(1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_20) Manage(#SEP_1) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_21) Manage(#SEP_2) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_22) Manage(#SEP_3) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_23) Manage(#SEP_4) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_24) Manage(#SEP_5) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_25) Manage(#SEP_6) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_26) Manage(#SEP_7) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_FWLI) NAME(#FWLI_42) Manage(#SEP_8) Parent(#FWLM_1)
DEFINE_COM CLASS(#PRIM_PANL) NAME(#PANL_TOP) Displayposition(2) Height(24) Left(0) Parent(#COM_OWNER) Tabposition(2) Tabstop(False) Themedrawstyle(Toolbar) Themestyle(Themed) Top(0) Visualstyle(#VS_WHITE9) Width(938)
DEFINE_COM CLASS(#PRIM_ATLI) NAME(#ATLI_1) Attachment(Top) Manage(#PANL_TOP) Parent(#ATLM_1)

*  =======================================================

*  Locally Defined Fields
*  =======================================================
DEFINE FIELD(#wOkPress) TYPE(*BOOLEAN)
DEFINE FIELD(#wReadOnly) TYPE(*BOOLEAN)
DEFINE FIELD(#wFixed) TYPE(*BOOLEAN)
DEFINE FIELD(#wToolbar) TYPE(*BOOLEAN)
DEFINE FIELD(#wFixDiv) TYPE(*STRING)
DEFINE FIELD(#wEndDiv) TYPE(*STRING)
DEFINE FIELD(#wChars) TYPE(*STRING)
DEFINE FIELD(#wFilePath) TYPE(*STRING)
DEFINE FIELD(#wFileName) REFFLD(#STD_PATH)
DEFINE FIELD(#wUserPath) REFFLD(#STD_PATH)
DEFINE FIELD(#wReturn) TYPE(*CHAR) LENGTH(2)

*  =======================================================
*  Events
*  =======================================================
DEFINE_EVT NAME(Changed)
DEFINE_EVT NAME(ImageAdded)

*  =======================================================

*  Properties
*  =======================================================
DEFINE_PTY NAME(OutputText) GET(GetOutputText) SET(SetOutputText)
DEFINE_PTY NAME(ShowToolbar) SET(SetShowToolbar)
DEFINE_PTY NAME(ImagePath) GET(*AUTO #STD_PATH) SET(SetImagePath)
DEFINE_PTY NAME(ReadOnly) GET(GetReadOnly) SET(SetReadOnly)
DEFINE_PTY NAME(FixedFont) GET(GetFixedFont) SET(SetFixedFont)
DEFINE_PTY NAME(ClipboardTools) GET(GetClipboardTools) SET(SetClipboardTools)
DEFINE_PTY NAME(AdvancedEditing) GET(GetAdvancedEditing) SET(SetAdvancedEditing)

*  =======================================================

*  Property Routines
*  =======================================================
PTYROUTINE NAME(GetOutputText)
DEFINE_MAP FOR(*OUTPUT) CLASS(#STD_MEMO) NAME(#oText)

* Loop through DocumentHTML character at a time to ignore low/high ASCII values
#STD_MEMO := #VA_HTMLED.DocumentHTML
BEGIN_LOOP USING(#STD_NUM) TO(#STD_MEMO.CurChars)
CONTINUE IF(#wChars.Contains( #STD_MEMO.Substring( #STD_NUM 1 ) ).Not)
#oText += #STD_MEMO.Substring( #STD_NUM 1 )
END_LOOP
ENDROUTINE

PTYROUTINE NAME(SetOutputText)
DEFINE_MAP FOR(*INPUT) CLASS(#STD_MEMO) NAME(#iText)

* Replace &nbsp; with ASCII space for proper word wrapping
#VA_HTMLED.DocumentHTML #WorkText := #iText.ReplaceAll( ‘ ‘ ‘ ‘ )

* Check if Text contains Fixed Width Default tag
IF (#VA_HTMLED.DocumentHTML.Contains( #wFixDiv ))
#COM_OWNER.FixedFont := True
ELSE
#COM_OWNER.FixedFont := False
ENDIF
ENDROUTINE

PTYROUTINE NAME(SetShowToolbar)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#iShow)

* Hide/Show Markup Toolbar
#PANL_TOP.Visible #wToolbar := #iShow
ENDROUTINE

PTYROUTINE NAME(SetImagePath)
DEFINE_MAP FOR(*INPUT) CLASS(#STD_PATH) NAME(#iPath)

* Sets path to attach images from, do not allow images if not set
#STD_PATH := #iPath
IF (#STD_PATH = ”)
#BIMAGE.Visible := False
ELSE
#BIMAGE.Visible := True
ENDIF
ENDROUTINE

PTYROUTINE NAME(SetReadOnly)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#iReadOnly)

* Set component to read only
IF (#iReadOnly)
#PANL_TOOLS.Visible := False
#VA_HTMLED.EditorEnabled := False
ELSE

* When set to Edit, check Show Toolbar property
IF (#wToolbar)
#PANL_TOOLS.Visible := True
ENDIF
#VA_HTMLED.EditorEnabled := True

* Restore Default CSSText property
#VA_HTMLED.CSSText := ‘body { font: 12px Arial, Tahoma, Verdana; color: #000000; background-color: rgb(255,255,255); } table { border: 1px solid #3B5E91; border-collapse: collapse; padding: 5px; font: 12px Arial, Tahoma, Verdana; color: #000000; background-color: rgb(255,255,255); } td { border: 1px solid #3B5E91; border-collapse: collapse; }’
ENDIF
#wReadOnly := #iReadOnly
ENDROUTINE

PTYROUTINE NAME(GetReadOnly)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#oReadOnly)
IF (#wReadOnly <> True)
#wReadOnly := False
ENDIF
#oReadOnly := #wReadOnly
ENDROUTINE

PTYROUTINE NAME(SetFixedFont)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#iFixedFont)

* Set Default fon to Fixed Width
IF (#iFixedFont)
IF (#VA_HTMLED.DocumentHTML.Contains( #wFixDiv ).Not)
#VA_HTMLED.DocumentHTML := #wFixDiv + #VA_HTMLED.DocumentHTML + #wEndDiv
ENDIF
ELSE
IF (#VA_HTMLED.DocumentHTML.Contains( #wFixDiv ))
#VA_HTMLED.DocumentHTML := #VA_HTMLED.DocumentHTML.LeftTrim( #wFixDiv ).RightTrim( #wEndDiv )
ENDIF
ENDIF
#wFixed := #iFixedFont
ENDROUTINE

PTYROUTINE NAME(GetFixedFont)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#oFixedFont)

* Return FixedFont Property
#oFixedFont := #wFixed
ENDROUTINE

PTYROUTINE NAME(SetClipboardTools)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#iClipboardTools)

* Hide/Show Clipboard Button
#BCUT.Visible #BCOPY.Visible #BPASTE.Visible #SEP_1.Visible := #iClipboardTools
ENDROUTINE

PTYROUTINE NAME(GetClipboardTools)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#oClipboardTools)

* Return ClipboardTools Property
#oClipboardTools := #BCUT.Visible
ENDROUTINE

PTYROUTINE NAME(SetAdvancedEditing)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_BOLN) NAME(#iAdvancedEditing)

* Hide/Show Advanced Editing Features
#SEP_4.Visible #SEP_6.Visible #SEP_7.Visible #SEP_8.Visible #BINDENT.Visible #BOUTDENT.Visible #BIMAGE.Visible #BLINK.Visible #BTABLE.Visible #BUNDO.Visible #BREDO.Visible #BSPELL.Visible := #iAdvancedEditing
ENDROUTINE

PTYROUTINE NAME(GetAdvancedEditing)
DEFINE_MAP FOR(*OUTPUT) CLASS(#PRIM_BOLN) NAME(#oAdvancedEditing)

* Return AdvancedEditing Property
#oAdvancedEditing := #BSPELL.Visible
ENDROUTINE

*  =======================================================
*  Event Routines
*  =======================================================
EVTROUTINE HANDLING(#COM_OWNER.CreateInstance) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)

* Set Initial Values for Local Fields
#wChars := *QUOTE.Concat( ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890`~!@#$%^&*()-_=+[]{}\|;:”/?.>,<‘ )
#wFixDiv := ‘
#wEndDiv := ‘</div>’
#wToolbar := True
#wReadOnly := False
#wFixed := False
ENDROUTINE

EVTROUTINE HANDLING(#COM_OWNER.Initialize) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)
#VA_HTMLED.Realize

* Hide Editor Context Menu Options
#VA_HTMLED.HiddenContextMenus := ‘MiscSource;MiscStyle;MiscRevert;ImgNewLink;ImgLinkRemove;ImgLink;Links;LinkCut;LinkCopy;LinkPaste;LinkEdit;LinkRemove;’

* Set CSS Text
#VA_HTMLED.CSSText := ‘body { font: 12px Arial, Tahoma, Verdana; color: #000000; background-color: rgb(255,255,255); } table { border: 1px solid #3B5E91; border-collapse: collapse; padding: 5px; font: 12px Arial, Tahoma, Verdana; color: #000000; background-color: rgb(255,255,255); } td { border: 1px solid #3B5E91; border-collapse: collapse; }’
ENDROUTINE

EVTROUTINE HANDLING(#BPASTE.Click)
#COM_OWNER.PasteClipboard
ENDROUTINE

MTHROUTINE NAME(PasteClipboard)

* Paste from Windows clipboard
#VA_HTMLED.paste_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BCOPY.Click)
#COM_OWNER.CopyClipboard
ENDROUTINE

MTHROUTINE NAME(CopyClipboard)

* Copy to Windows clipboard
#VA_HTMLED.copy_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BCUT.Click)
#COM_OWNER.CutClipboard
ENDROUTINE

MTHROUTINE NAME(CutClipboard)
* Cut to Windows clipboard
#VA_HTMLED.cut_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BBOLD.Click)
#COM_OWNER.BoldText
ENDROUTINE

MTHROUTINE NAME(BoldText)

* Bold selected text
#VA_HTMLED.bold_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BITALIC.Click)
#COM_OWNER.ItalicText
ENDROUTINE

MTHROUTINE NAME(ItalicText)

* Italic selected text
#VA_HTMLED.italic_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BUNDERLINE.Click)
#COM_OWNER.UnderlineText
ENDROUTINE

MTHROUTINE NAME(UnderlineText)

* Underline selected text
#VA_HTMLED.underline_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BSTRIKE.Click)
#COM_OWNER.StrikethroughText
ENDROUTINE

MTHROUTINE NAME(StrikethroughText)

* Strikethrough selected text
#VA_HTMLED.strike_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BFONT.Click)
#COM_OWNER.CustomFont
ENDROUTINE

MTHROUTINE NAME(CustomFont)

* Customize font settings
#VA_HTMLED.change_Font

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BLEFT.Click)
#COM_OWNER.LeftJustify
ENDROUTINE

MTHROUTINE NAME(LeftJustify)

* Left Align
#VA_HTMLED.Left_Justify

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BCENTER.Click)
#COM_OWNER.CenterJustify
ENDROUTINE

MTHROUTINE NAME(CenterJustify)

* Center Align
#VA_HTMLED.Center_Justify

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BRIGHT.Click)
#COM_OWNER.RightJustify
ENDROUTINE

MTHROUTINE NAME(RightJustify)

* Right Align
#VA_HTMLED.Right_Justify

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BINDENT.Click)
#COM_OWNER.IndentText
ENDROUTINE

MTHROUTINE NAME(IndentText)

* Indents entire block element (<p><div>)
#VA_HTMLED.indent_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BOUTDENT.Click)
#COM_OWNER.OutdentText
ENDROUTINE

MTHROUTINE NAME(OutdentText)

* Unindents entire block element (
#VA_HTMLED.unindent_document

)

 

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BORDERED.Click)
#COM_OWNER.OrderedList
ENDROUTINE

MTHROUTINE NAME(OrderedList)

* Create Numbered List
#VA_HTMLED.numbered_list

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BUNORDERED.Click)
#COM_OWNER.UnorderedList
ENDROUTINE

MTHROUTINE NAME(UnorderedList)

* Create Unnmbered List (Bullets)
#VA_HTMLED.unnumbered_list

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BIMAGE.Click)
#COM_OWNER.InsertImage
ENDROUTINE

MTHROUTINE NAME(InsertImage)

* Insert Image from File
INVOKE METHOD(#SYS_APPLN.CreateFileOpenDialog) Result(#OpenDialog)
#OpenDialog.Title := ‘Select File’
#OpenDialog.HideReadOnly := False
#OpenDialog.ExplorerStyle := True

* Set Initial Path to ImagePath property
#OpenDialog.InitialDir := #STD_PATH
#OpenDialog.MultiSelect := False
INVOKE METHOD(#OpenDialog.Show) Okpressed(#wOkPress)
IF ((#wOkPress) *And (#OpenDialog.FileCount = 0))

* Image was selected
#wFilePath := #OpenDialog.File
IF (#wFilePath.Contains( ‘\’ ))
#wUserPath := #wFilePath.Substring( 1 (#wFilePath.LastPositionOf( ‘\’ )) )
#wFileName := #wFilePath.Substring( (#wFilePath.LastPositionOf( ‘\’ ) + 1) )
IF (#wUserPath <> #STD_PATH)
USE BUILTIN(OV_FILE_SERVICE) WITH_ARGS(COPY_FILE #wFilePath (#STD_PATH + #wFileName)) TO_GET(#wReturn)
IF (#wReturn = ‘OK’)
SIGNAL EVENT(ImageAdded)
#wFilePath := (#STD_PATH + #wFileName)
ENDIF
ENDIF
ELSE
IF (#wFilePath.Contains( ‘/’ ))
#wUserPath := #wFilePath.Substring( 1 (#wFilePath.LastPositionOf( ‘/’ )) )
#wFileName := #wFilePath.Substring( (#wFilePath.LastPositionOf( ‘/’ ) + 1) )
IF (#wUserPath <> #STD_PATH)
USE BUILTIN(OV_FILE_SERVICE) WITH_ARGS(COPY_FILE #wFilePath (#STD_PATH + #wFileName)) TO_GET(#wReturn)
IF (#wReturn = ‘OK’)
SIGNAL EVENT(ImageAdded)
#wFilePath := (#STD_PATH + #wFileName)
ENDIF
ENDIF
ENDIF
ENDIF

* This code provided for storing images on web-facing IFS path: 
* Format  image path using proper http path ensures images will be visible on any HTML/PDF printouts
* IF (#wFilePath.Uppercase.Contains( ‘//LocalSystemI’ ))
* #wFilePath := #wFilePath.Lowercase.Replace( ‘LocalSystemI’ ‘server.domain.com’ )
* ELSE
*IF (#wFilePath.Uppercase.Contains( ‘\\LocalSystemI’ ))
*#wFilePath := #wFilePath.Lowercase.ReplaceAll( ‘\’ ‘/’ ).ReplaceAll( ‘LocalSystemI’ ‘server.domain.com’ )
*ENDIF
*ENDIF#VA_HTMLED.insertImageFromURL( #wFilePath )
*ENDIF

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BLINK.Click)
#COM_OWNER.InsertLink
ENDROUTINE

MTHROUTINE NAME(InsertLink)

* Create Hyperlink
#VA_HTMLED.insert_Link

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BTABLE.Click)
#COM_OWNER.InsertTable
ENDROUTINE

MTHROUTINE NAME(InsertTable)

* Displays the Rich-Text component’s inert HTML table dialog
#VA_HTMLED.Table_Document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BUNDO.Click)
#COM_OWNER.Undo
ENDROUTINE

MTHROUTINE NAME(Undo)

* Undo (CTRL + Z)
#VA_HTMLED.undo_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BREDO.Click)
#COM_OWNER.Redo
ENDROUTINE

MTHROUTINE NAME(Redo)

* Redo (CTRL + Y)
#VA_HTMLED.redo_document

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

EVTROUTINE HANDLING(#BSPELL.Click)
#COM_OWNER.CheckSpelling
ENDROUTINE

MTHROUTINE NAME(CheckSpelling)

* Spellcheck document
#VA_HTMLED.SpellCheck
ENDROUTINE

EVTROUTINE HANDLING(#VA_HTMLED.ASCIIKeyPress) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)

* Check for Changes
#COM_OWNER.CheckChanges
ENDROUTINE

MTHROUTINE NAME(CheckChanges)

* Signal Changed on Change of Output Text
IF (#VA_HTMLED.DocumentHTML <> #WorkText)
SIGNAL EVENT(Changed)
#WorkText := #VA_HTMLED.DocumentHTML
ENDIF
ENDROUTINE

END_COM
 

Managing Treeview Checkboxes

It’s been several months since my last post. I have taken a new position with an ISV located in Atlanta, Georgia. My new position has afforded me the conundrum of having more to blog about and less time to blog. In the current economic climate one cannot complain about being too busy…better than the alternative.

I came across a simple subject to add to the blog today. The code below contains two simple methods for keeping track of checkboxes within treeviews enclosed in a sample form to illustrate the behavior. This code is reusable and could even be added to a treeview superclass to handle the checking, unchecking, and graying of treeview items on multiple levels. It is really quite simple but is something that can be time consuming if not handled dynamically.

*  =======================================================
*   Treeview Sample Form
*  =======================================================
FUNCTION OPTIONS(*DIRECT)

BEGIN_COM ROLE(*EXTENDS #PRIM_FORM) Clientheight(362) Clientwidth(384) Height(400) Layoutmanager(#ATLM_1) Left(366) Top(132) Width(400)
DEFINE_COM CLASS(#PRIM_TRVW) NAME(#TRVW_1) Componentversion(2) Displayposition(1) Height(362) Keyboardpositioning(SortColumn) Left(0) Managechildren(True) Parent(#COM_OWNER) Tabposition(1) Top(0) Width(384)
DEFINE_COM CLASS(#PRIM_TVCL) NAME(#TVCL_1) Checkboxes(True) Displayposition(1) Keyposition(1) Level(1) Parent(#TRVW_1) Sortposition(1) Source(#STD_CODE)
DEFINE_COM CLASS(#PRIM_TVCL) NAME(#TVCL_2) Checkboxes(True) Displayposition(1) Keyposition(1) Level(2) Parent(#TRVW_1) Sortposition(1) Source(#STD_DESC)
DEFINE_COM CLASS(#PRIM_TVCL) NAME(#TVCL_3) Checkboxes(True) Displayposition(1) Keyposition(1) Level(3) Parent(#TRVW_1) Sortposition(1) Source(#STD_DESCL)
DEFINE_COM CLASS(#PRIM_ATLM) NAME(#ATLM_1)
DEFINE_COM CLASS(#PRIM_ATLI) NAME(#ATLI_1) Attachment(Center) Manage(#TRVW_1) Parent(#ATLM_1)
DEFINE FIELD(#wLevel1) TYPE(*INT)
DEFINE FIELD(#wLevel2) TYPE(*INT)
DEFINE FIELD(#wLevel3) TYPE(*INT)
EVTROUTINE HANDLING(#COM_OWNER.Initialize)
BEGIN_LOOP USING(#wLevel1) TO(3)
#STD_CODE := ’00’ + #wLevel1.AsString
BEGIN_LOOP USING(#wLevel2) TO(3)
#STD_DESC := #STD_CODE + ‘ – Description 0’ + #wLevel2.AsString
BEGIN_LOOP USING(#wLevel3) TO(3)
#STD_DESCL := #STD_DESC + ‘ – Long Description 0’ + #wLevel3.AsString
ADD_ENTRY TO_LIST(#TRVW_1)
#TRVW_1.CurrentItem.Expanded := True
END_LOOP
END_LOOP
END_LOOP
ENDROUTINE

*  =======================================================
*   Update Treeview Parent/Child Items on Change
*  =======================================================
EVTROUTINE HANDLING(#TRVW_1.ItemChanged) OPTIONS(*NOCLEARMESSAGES *NOCLEARERRORS)

* Set Child Items Checkbox State if Current Item has Child Items
IF (#TRVW_1.CurrentItem.HasChildren = ‘YES’)
#COM_OWNER.SetChildCheckBoxes( #TRVW_1.CurrentItem )
ENDIF

* Set Parent Item Checkbox State fif Current Item is Child Item
IF (#TRVW_1.CurrentItem.Level > 1)
#COM_OWNER.SetParentCheckBoxes( #TRVW_1.CurrentItem )
ENDIF
ENDROUTINE

*  =======================================================
*   Set Parent Item Checkbox State from Child Item
*  =======================================================
MTHROUTINE NAME(SetParentCheckboxes)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_TVIT) NAME(#ChildItem) PASS(*BY_REFERENCE)

* Assume All Children are Unchecked
#ChildItem.ParentItem.Checked := False
FOR EACH(#Item) IN(#ChildItem.ParentItem.Items)
IF (#Item.Checked = True)

* Set to Checked if One Child Item is Checked
#Item.ParentItem.Checked := True
LEAVE
ENDIF
ENDFOR
IF (#ChildItem.ParentItem.Checked = True)
FOR EACH(#Item) IN(#ChildItem.ParentItem.Items)
IF ((#Item.Checked = False) *Or (#Item.Checked = Grayed))

* Set to Grayed if One Child Item is Unchecked or Grayed
#Item.ParentItem.Checked := Grayed
LEAVE
ENDIF
ENDFOR
ELSE
FOR EACH(#Item) IN(#ChildItem.ParentItem.Items)
IF (#Item.Checked = Grayed)

* Set to Grayed if One Child Item is Grayed
#Item.ParentItem.Checked := Grayed
LEAVE
ENDIF
ENDFOR
ENDIF
IF (#ChildItem.ParentItem.Level > 1)

* Continue Updating Parent Items until Root Item
#COM_OWNER.SetParentCheckBoxes( #ChildItem.ParentItem )
ENDIF
ENDROUTINE

*  =======================================================
*   Set Child Item Checkbox State from Parent Item
*  =======================================================
MTHROUTINE NAME(SetChildCheckboxes)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_TVIT) NAME(#ParentItem) PASS(*BY_REFERENCE)
IF (#ParentItem.Checked <> Grayed)
FOR EACH(#Item) IN(#ParentItem.Items)

* Set Item to Parent Item Checkbox Status if Checked or Unchecked Only
#Item.Checked := #ParentItem.Checked
IF (#Item.HasChildren = ‘YES’)

* Continue Updating Child Items until Lowest Level Reached
#COM_OWNER.SetChildCheckBoxes( #Item )
ENDIF
ENDFOR
ENDIF
ENDROUTINE
END_COM

UPS Address Validation

One of my projects that elicited numerous inquiries at the Lansa Solutions Summit was using SOAP for validating and classifying street-level addresses. The builtin-function below is how I perform this validation request from Visual Lansa Forms and is not optimized for batch processing. It contains several custom blocks of code that can be modified or removed, but suited my purposes. UPS requires an HTTP/XML request that is a two-part XML document: a authentication request and an address validation request. Integrator was unable to create a two-part XML document so I used STM_FILE BIFs to write the request. I also create and store XML documents in a /UPS/ directory on the IFS which would have to be changed accordingly. There are many repository fields that will need added or replaced. The function will handle US, Puerto Rico, and Canadian addresses but classification and/or validation may be incomplete for Canada and Puerto Rico Addresses…more information can be found on the UPS website or through a UPS account representative.

Note: A UPS account is required for using the street level validation service.

BIF definition function:

FUNCTION OPTIONS(*DIRECT *BUILTIN)
*  =======================================================
*   Builtin Function Definition
*  =======================================================
DEFINE FIELD(#BIF_NAME) TYPE(*CHAR) LENGTH(020) DESC(‘UPS Online Address Validation’) DEFAULT(‘UPSValidate’)

*  =======================================================
*   Builtin Function Arguments
*  =======================================================
DEFINE FIELD(#BIF_ARG01) REFFLD(#STD_OBJ) DESC(‘Action Requested’)
DEFINE FIELD(#BIF_ARG02) REFFLD(#MSTAD1) DESC(‘Address Line 1’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG03) REFFLD(#MSTAD2) DESC(‘Address Line 2’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG04) REFFLD(#MSTAD3) DESC(‘Address Line 3’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG05) REFFLD(#MSTCTY) DESC(‘City’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG06) REFFLD(#MSTSTA) DESC(‘State’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG07) REFFLD(#NSTPSC) DESC(‘Zip Code’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG08) REFFLD(#CSTCNT) DESC(‘Country’) DEFAULT(‘840’)

*  =======================================================
*   Builtin Function Results
*  =======================================================

DEFINE FIELD(#BIF_RET01) REFFLD(#RETCD) DESC(‘Status’)
DEFINE FIELD(#BIF_RET02) REFFLD(#CADRCL) DESC(‘Address Classification’) DEFAULT(”)
DEFINE FIELD(#BIF_RET03) REFFLD(#MSTAD1) DESC(‘Address Line 1’) DEFAULT(”)
DEFINE FIELD(#BIF_RET04) REFFLD(#MSTAD2) DESC(‘Address Line 2’) DEFAULT(”)
DEFINE FIELD(#BIF_RET05) REFFLD(#MSTAD3) DESC(‘Address Line 3’) DEFAULT(”)
DEFINE FIELD(#BIF_RET06) REFFLD(#MSTCTY) DESC(‘City’) DEFAULT(”)
DEFINE FIELD(#BIF_RET07) REFFLD(#MSTSTA) DESC(‘State’) DEFAULT(”)
DEFINE FIELD(#BIF_RET08) REFFLD(#NSTPSC) DESC(‘Zip Code’) DEFAULT(”)
DEFINE FIELD(#BIF_RET09) REFFLD(#CSTPSX) DESC(‘Zip Code Extension’) DEFAULT(”)
DEFINE FIELD(#BIF_RET10) REFFLD(#STD_PATH) DESC(‘Path to XML Response’) DEFAULT(”)

*  =======================================================
*   Locally Defined Fields
*  =======================================================

DEFINE FIELD(#vOpen) TYPE(*BOOLEAN)

*  =======================================================
*   Main Processing
*  =======================================================
EXCHANGE FIELDS(#BIF_ARG01 #BIF_ARG02 #BIF_ARG03 #BIF_ARG04 #BIF_ARG05 #BIF_ARG06 #BIF_ARG07 #BIF_ARG08)

* Call UPS Address Validation Server Function
IF (*CPUTYPE = ‘AS400’)
CALL PROCESS(*DIRECT) FUNCTION(UPSVALS)
ELSE
USE BUILTIN(CALL_SERVER_FUNCTION) WITH_ARGS(*SSERVER_SSN UPSVALS Y Y) TO_GET(#STD_CODE2)
IF (#STD_CODE2 = ‘ER’)
#BIF_RET01 := ‘ER’
ENDIF
ENDIF

* If running in Batch mode set function for heavy usage (JSM services remain loaded)
IF (#vOpen)
USE BUILTIN(SET_FOR_HEAVY_USAGE)
ELSE
USE BUILTIN(SET_FOR_LIGHT_USAGE)
ENDIF

* Return to Calling Program
RETURN 

 

Server function:

FUNCTION OPTIONS(*DIRECT)
*  =======================================================
*   Builtin Function Arguments
*  =======================================================

DEFINE FIELD(#BIF_ARG01) REFFLD(#STD_OBJ) DESC(‘Action Requested’)
DEFINE FIELD(#BIF_ARG02) REFFLD(#MSTAD1) DESC(‘Address Line 1’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG03) REFFLD(#MSTAD2) DESC(‘Address Line 2’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG04) REFFLD(#MSTAD3) DESC(‘Address Line 3’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG05) REFFLD(#MSTCTY) DESC(‘City’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG06) REFFLD(#MSTSTA) DESC(‘State’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG07) REFFLD(#NSTPSC) DESC(‘Zip Code’) DEFAULT(”)
DEFINE FIELD(#BIF_ARG08) REFFLD(#CSTCNT) DESC(‘Country’) DEFAULT(‘840’)

*  =======================================================
*   Builtin Function Results
*  =======================================================

DEFINE FIELD(#BIF_RET01) REFFLD(#RETCD) DESC(‘Status’)
DEFINE FIELD(#BIF_RET02) REFFLD(#CADRCL) DESC(‘Address Classification’) DEFAULT(”)
DEFINE FIELD(#BIF_RET03) REFFLD(#MSTAD1) DESC(‘Address Line 1’) DEFAULT(”)
DEFINE FIELD(#BIF_RET04) REFFLD(#MSTAD2) DESC(‘Address Line 2’) DEFAULT(”)
DEFINE FIELD(#BIF_RET05) REFFLD(#MSTAD3) DESC(‘Address Line 3’) DEFAULT(”)
DEFINE FIELD(#BIF_RET06) REFFLD(#MSTCTY) DESC(‘City’) DEFAULT(”)
DEFINE FIELD(#BIF_RET07) REFFLD(#MSTSTA) DESC(‘State’) DEFAULT(”)
DEFINE FIELD(#BIF_RET08) REFFLD(#NSTPSC) DESC(‘Zip Code’) DEFAULT(”)
DEFINE FIELD(#BIF_RET09) REFFLD(#CSTPSX) DESC(‘Zip Code Extension’) DEFAULT(”)
DEFINE FIELD(#BIF_RET10) REFFLD(#STD_PATH) DESC(‘Path to XML Response’) DEFAULT(”)

*  =======================================================
*   Locally Defined Fields
*  =======================================================
DEFINE FIELD(#vStmFile) TYPE(*DEC) LENGTH(3) DECIMALS(0)
DEFINE FIELD(#vRetCode) TYPE(*CHAR) LENGTH(2)
DEFINE FIELD(#vOpen) TYPE(*BOOLEAN)
DEFINE FIELD(#vHTTP) REFFLD(#JSMHDL)
DEFINE FIELD(#vParser) REFFLD(#JSMHDL)
DEFINE FIELD(#vAttn) REFFLD(#MSTAD1)
DEFINE FIELD(#vStreet) REFFLD(#MSTAD1)

*  =======================================================
*   Main Processing
*  =======================================================

#BIF_RET01 := ‘OK’
CASE (#BIF_ARG01.Uppercase)

* When passed the OPEN action open explicitly start the JSM services and wait for VALIDATE request(s)
WHEN (= ‘OPEN’)
IF (#vOpen.Not)
USE BUILTIN(JSMX_OPEN) TO_GET(#JSSTS #JSMSG #vHTTP)
USE BUILTIN(JSMX_OPEN) TO_GET(#JSSTS #JSMSG #vParser)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP ‘service_load service(httpservice)’) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘service_load service(xmlparserservice)’) TO_GET(#JSSTS #JSMSG)
#vOpen := True
ENDIF

* When passed the VALIDATE action, validate the address passed address
WHEN (= ‘VALIDATE’)
IF (#vOpen)

 * If JSM has been opened by OPEN action, validate address only
EXECUTE SUBROUTINE(Validate)
ELSE

* If JSM has not been opened by OPEN action, start the JSM services, validate the passed address, and end the JSM services
IF (*CPUTYPE = ‘AS400’)
USE BUILTIN(JSMX_OPEN) TO_GET(#JSSTS #JSMSG #vHTTP)
USE BUILTIN(JSMX_OPEN) TO_GET(#JSSTS #JSMSG #vParser)
ELSE
USE BUILTIN(JSMX_OPEN) WITH_ARGS(ROCKYA) TO_GET(#JSSTS #JSMSG #vHTTP)
USE BUILTIN(JSMX_OPEN) WITH_ARGS(ROCKYA) TO_GET(#JSSTS #JSMSG #vParser)
ENDIF
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP ‘service_load service(httpservice)’) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘service_load service(xmlparserservice)’) TO_GET(#JSSTS #JSMSG)
EXECUTE SUBROUTINE(Validate)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP ‘service_unload’) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘service_unload’) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_CLOSE) WITH_ARGS(#vHTTP) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_CLOSE) WITH_ARGS(#vParser) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(OV_FILE_SERVICE) WITH_ARGS(REMOVE_FILE (‘/UPS/Request’ + *JOBNBR + ‘.xml’) FORCE)

 * Retain Response XML for Administrator Users using Lookup Tool
IF (*USER = ‘ADMIN’)
USE BUILTIN(OV_FILE_SERVICE) WITH_ARGS(REMOVE_FILE (‘/UPS/Response’ + *JOBNBR + ‘.xml’) FORCE)
ENDIF
#vOpen := False
ENDIF

WHEN (= ‘CLOSE’) 

* When passed the CLOSE action open explicitly end the JSM services
IF (#vOpen)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP ‘service_unload’) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘service_unload’) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_CLOSE) WITH_ARGS(#vHTTP) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_CLOSE) WITH_ARGS(#vParser) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(OV_FILE_SERVICE) WITH_ARGS(REMOVE_FILE (‘/UPS/Request’ + *JOBNBR + ‘.xml’) FORCE)
USE BUILTIN(OV_FILE_SERVICE) WITH_ARGS(REMOVE_FILE (‘/UPS/Response’ + *JOBNBR + ‘.xml’) FORCE)
#vOpen := False
ENDIF
ENDCASE

* If running in Batch mode set function for heavy usage (JSM services remain loaded)
IF (#vOpen)
USE BUILTIN(SET_FOR_HEAVY_USAGE)
ELSE
USE BUILTIN(SET_FOR_LIGHT_USAGE)
ENDIF
EXCHANGE FIELDS(#vOpen #BIF_RET01 #BIF_RET02 #BIF_RET03 #BIF_RET04 #BIF_RET05 #BIF_RET06 #BIF_RET07 #BIF_RET08 #BIF_RET09 #BIF_RET10)

 * Return to Calling Program
RETURN

 * Lookup Address Matches
SUBROUTINE NAME(Validate)
EXECUTE SUBROUTINE(Lookup)

* If Address Not Found Returned
IF (#BIF_RET01 = ‘NA’)

* If 2 Address Lines Passed, Attempt with various configurations of address lines
IF ((#BIF_ARG02 <> ”) *And (#BIF_ARG03 <> ”))

* Save Passed Address Lines
#MSTAD1 := #BIF_ARG02
#MSTAD2 := #BIF_ARG03

* Reverse Address Lines
#BIF_ARG02 := #MSTAD2
#BIF_ARG03 := #MSTAD1
EXECUTE SUBROUTINE(Lookup)
IF ((#BIF_RET01 = ‘VA’) *Or (#BIF_RET01 = ‘AA’))

* Return Address Format Error with Valid Format
#BIF_RET01 := ‘AF’
ELSE

 * Address Line 1 Only
#BIF_ARG02 := #MSTAD1
#BIF_ARG03 := ”
EXECUTE SUBROUTINE(Lookup)
IF ((#BIF_RET01 = ‘VA’) *Or (#BIF_RET01 = ‘AA’))

 * Return Address Format Error with Valid Format
#BIF_RET01 := ‘AF’
ELSE

 * Address Line 2 Only
#BIF_ARG02 := #MSTAD2
#BIF_ARG03 := ”
EXECUTE SUBROUTINE(Lookup)
IF ((#BIF_RET01 = ‘VA’) *Or (#BIF_RET01 = ‘AA’))

 * Return Address Format Error with Valid Format
#BIF_RET01 := ‘AF’
ELSE

 * Unable to Find Valid Address
#BIF_RET01 := ‘NA’
ENDIF
ENDIF
ENDIF
ENDIF

* If Correct Format not found from swithing Address Lines, Attempt to purge ATTN data from Address Lines
IF ((#BIF_RET01 = ‘NA’) *And (#BIF_ARG02.Uppercase.Contains( ‘ATTN’ )) *And ((#BIF_ARG02.Contains( ‘1’ )) *Or (#BIF_ARG02.Contains( ‘2’ )) *Or (#BIF_ARG02.Contains( ‘3’ )) *Or (#BIF_ARG02.Contains( ‘4’ )) *Or (#BIF_ARG02.Contains( ‘5’ )) *Or (#BIF_ARG02.Contains( ‘6’ )) *Or (#BIF_ARG02.Contains( ‘7’ )) *Or (#BIF_ARG02.Contains( ‘8’ )) *Or (#BIF_ARG02.Contains( ‘9’ ))) *And ((#BIF_ARG03 = ”) *Or (#BIF_ARG04 = ”)))

 * Save Passed Address Lines
#MSTAD1 := #BIF_ARG02
#MSTAD2 := #BIF_ARG03
#MSTAD3 := #BIF_ARG04

* Address Line Begins with ATTN data and may contain Street Number
IF (#BIF_ARG02.Uppercase.LeftMost( 4 ) = ‘ATTN’)
#vAttn #vStreet := ”
DOUNTIL (#BIF_ARG02.CurChars = 0)
IF (#BIF_ARG02.Substring( 1 1 ).IsNumber.Not)
#vAttn += #BIF_ARG02.Substring( 1 1 )
#BIF_ARG02 := #BIF_ARG02.Substring( 2 )
ELSE
#vStreet := #BIF_ARG02
#BIF_ARG02 := ”
LEAVE
ENDIF
ENDUNTIL
ELSE

* Address Line Starts with Street Number, but contains ATTN data
IF (#BIF_ARG02.Uppercase.LeftMost( 1 ).IsNumber)
#vAttn #vStreet := ”
#vAttn := #BIF_ARG02.Substring( #BIF_ARG02.Uppercase.PositionOf( ‘ATTN’ ) )
#vStreet := #BIF_ARG02.Substring( 1 (#BIF_ARG02.Uppercase.PositionOf( ‘ATTN’ ) – 1) )
ENDIF
ENDIF

* If Address Line 2 is blank, split Address Line 1 to Line 1 and 2
IF (#MSTAD2 = ”)
#BIF_ARG02 := #vAttn
#BIF_ARG03 := #vStreet
ELSE

* If Address Line 3 is blank, Move Address Line 2 to Line 3 split Address Line 1 to Line 1 and 2
IF (#MSTAD3 = ”)
#BIF_ARG02 := #vAttn
#BIF_ARG03 := #vStreet
#BIF_ARG04 := #MSTAD2
ENDIF
ENDIF

* Attempt to Validate reformatted Address
EXECUTE SUBROUTINE(Lookup)
IF ((#BIF_RET01 = ‘VA’) *Or (#BIF_RET01 = ‘AA’))

* Return Address Format Error with Valid Format
#BIF_RET01 := ‘AF’
ELSE

* Unable to Find Valid Address
#BIF_RET01 := ‘NA’
ENDIF
ENDIF
ELSE

* City or Zip Returned does not match City or Zip Passed for Valid Address
IF ((#BIF_RET07 <> #BIF_ARG06) *Or (#BIF_RET08 <> #BIF_ARG07) *And (#BIF_RET01 = ‘VA’))
* If City and State match Returned Values and only Zip Code differs consider as Valid Address
IF ((#BIF_ARG05 = #BIF_RET06) *And (#BIF_ARG06 = #BIF_RET07))
#BIF_RET01 := ‘VA’
ELSE

* If City or State do not match Returned Values and Zip Code differs consider as No Address Found
#BIF_RET01 := ‘NA’
ENDIF
ELSE

* If Valid or Ambiguous Status returned and Address Classification (U) Unknown, try to get classification from Returned Address
IF (((#BIF_RET01 = ‘VA’) *Or (#BIF_RET01 = ‘AA’)) *And (#BIF_RET02 = ‘U’) *And (#BIF_ARG08 = ‘840’))
#STD_CODE2 := #BIF_RET01
#BIF_ARG02 := #BIF_RET03
#BIF_ARG03 := #BIF_RET04
#BIF_ARG04 := #BIF_RET05
#BIF_ARG05 := #BIF_RET06
#BIF_ARG06 := #BIF_RET07
#BIF_ARG07 := #BIF_RET08
EXECUTE SUBROUTINE(Lookup)

* Retain Original Status
#BIF_RET01 := #STD_CODE2
ENDIF
ENDIF
ENDIF
ENDROUTINE

* Validate Passed Address
SUBROUTINE NAME(Lookup)

* Reset Return Values
#BIF_RET01 := ‘OK’
#BIF_RET02 #BIF_RET03 #BIF_RET04 #BIF_RET05 #BIF_RET06 #BIF_RET07 #BIF_RET08 #BIF_RET09 := ”

* Verify Address Arguments Passed
IF (#BIF_ARG02 = ”)

* Street Address not passed
#BIF_RET01 := ‘IN’
#BIF_RET03 := ‘Address must be passed to Built in Function’
ENDIF
IF ((#BIF_ARG05 = ”) *And (#BIF_ARG06 = ”) *And (#BIF_ARG07 = ”) *And (#BIF_RET01 = ‘OK’))

* City and State / Zip Code not passed
#BIF_RET01 := ‘IN’
#BIF_RET03 := ‘City, State and/or Zip Code must be passed to Built in Function’
ENDIF
IF (#BIF_ARG08.IsNumber.Not)

* Lookup Country Code from Abbreviation
FETCH FIELDS(#CCNTCD) FROM_FILE(MCNTL01) WITH_KEY(#BIF_ARG08) IO_ERROR(*NEXT)
IF_STATUS IS(*OKAY)
#BIF_ARG08 := #CCNTCD
ENDIF
ENDIF
IF ((#BIF_ARG08 <> ‘630’) *And (#BIF_ARG08 <> ‘124’))

* If Canada or Puerto Rico not explicitly passed, assume domestic address
#BIF_ARG08 := ‘840’
ENDIF
IF (#BIF_RET01 = ‘OK’)
USE BUILTIN(STM_FILE_OPEN) WITH_ARGS((‘/UPS/Request’ + *JOBNBR + ‘.xml’) ‘Write Text CodePage=00819 LineTerminator=CRLF’) TO_GET(#vStmFile #vRetCode)
IF (#vRetCode <> ER)

* Ensure No Special Characters are in Address Fields
IF ((#BIF_ARG02.Contains( ‘&’ )) *Or (#BIF_ARG03.Contains( ‘&’ )) *Or (#BIF_ARG04.Contains( ‘&’ )))
#BIF_ARG02 := #BIF_ARG02.ReplaceAll( ‘&’ ‘AND’ )
#BIF_ARG03 := #BIF_ARG03.ReplaceAll( ‘&’ ‘AND’ )
#BIF_ARG04 := #BIF_ARG04.ReplaceAll( ‘&’ ‘AND’ )
ENDIF

* Create Request XML
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<?xml version=”1.0″ ?>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<AccessRequest xml:lang=’ + *QUOTE + ‘en-US’ + *QUOTE + ‘>)’)) TO_GET(#vRetCode)

* Replace XXXXXXXX values with Access License, Username, and Password provided by UPS
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<AccessLicenseNumber>XXXXXXXX</AccessLicenseNumber>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<UserId>XXXXXXXX</UserId>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<Password>XXXXXXXX</Password>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘</AccessRequest>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<?xml version=”1.0″ ?>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<AddressValidationRequest xml:lang=’ + *QUOTE + ‘en-US’ + *QUOTE + ‘>)’)) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<Request>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<TransactionReference>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<CustomerContext /><XpciVersion>1.0001</XpciVersion>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘</TransactionReference>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<RequestAction>XAV</RequestAction>’) TO_GET(#vRetCode)

* Determine Request Type Based on Country Code
CASE (#BIF_ARG08)
WHEN (= ‘630’)

* Puerto Rico addresses can only be validated
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<RequestOption>1</RequestOption>’) TO_GET(#vRetCode)

WHEN (= ‘124’)

* Canada addresses can only be classified
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<RequestOption>2</RequestOption>’) TO_GET(#vRetCode)

OTHERWISE

* Domestic addresses can be validates and classified
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<RequestOption>3</RequestOption>’) TO_GET(#vRetCode)

ENDCASE

USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘</Request>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<MaximumListSize>1</MaximumListSize>’) TO_GET(#vRetCode)

* Address Key Format contains the address to be validated
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘<AddressKeyFormat>’) TO_GET(#vRetCode)
IF (#BIF_ARG02 <> ”)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<AddressLine>’ + #BIF_ARG02 + ‘</AddressLine>’)) TO_GET(#vRetCode)
ENDIF
IF (#BIF_ARG03 <> ”)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<AddressLine>’ + #BIF_ARG03 + ‘</AddressLine>’)) TO_GET(#vRetCode)
ENDIF
IF (#BIF_ARG04 <> ”)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<AddressLine>’ + #BIF_ARG04 + ‘</AddressLine>’)) TO_GET(#vRetCode)
ENDIF
IF (#BIF_ARG05 <> ”)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<PoliticalDivision2>’ + #BIF_ARG05 + ‘</PoliticalDivision2>’)) TO_GET(#vRetCode)
ENDIF
IF (#BIF_ARG06 <> ”)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<PoliticalDivision1>’ + #BIF_ARG06 + ‘</PoliticalDivision1>’)) TO_GET(#vRetCode)
ENDIF
IF (#BIF_ARG07 <> ”)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<PostcodePrimaryLow>’ + #BIF_ARG07 + ‘</PostcodePrimaryLow>’)) TO_GET(#vRetCode)
ENDIF

* Only USA, Canada, and Puerto Rico addresses can be validated
CASE (#BIF_ARG08)

WHEN (= ‘630’)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<CountryCode>PR</CountryCode>’)) TO_GET(#vRetCode)

WHEN (= ‘124’)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<CountryCode>CA</CountryCode>’)) TO_GET(#vRetCode)

OTHERWISE
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile (‘<CountryCode>US</CountryCode>’)) TO_GET(#vRetCode)

ENDCASE

USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘</AddressKeyFormat>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_WRITE) WITH_ARGS(#vStmFile ‘</AddressValidationRequest>’) TO_GET(#vRetCode)
USE BUILTIN(STM_FILE_CLOSE) WITH_ARGS(#vStmFile) TO_GET(#vRetCode)

* Contact UPS HTTP Service – UPS Test Server
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP (‘send handler(OutboundFile) file(/UPS/Request’ + *JOBNBR + ‘.xml) uri(/ups.app/xml/XAV)  host(wwwcie.ups.com) secure(*YES) wait(*YES) content(*XML)’)) TO_GET(#JSSTS #JSMSG)

* Contact UPS HTTP Service – UPS Live Server
* USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP (‘send handler(OutboundFile) file(/UPS/Request’ + *JOBNBR + ‘.xml) uri(/ups.app/xml/XAV)  host(onlinetools.ups.com) secure(*YES) wait(*YES) content(*XML)’)) TO_GET(#JSSTS #JSMSG)

IF (#JSSTS <> ‘OK’)

* Return Status as UN – JSM Unable to Send Request
#BIF_RET01 := ‘UN’
#BIF_RET03 := ‘Error sending HTTP request’
#BIF_RET04 := #JSMSG
ELSE

* Receive HTTP Response
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vHTTP (‘receive handler(InboundFile) to(/UPS/Response’ + *JOBNBR + ‘.xml)’)) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS <> ‘OK’)

* Return Status as ER – JSM Unable to Send Request
#BIF_RET01 := ‘UN’
#BIF_RET03 := ‘Error receiving HTTP response’
#BIF_RET04 := #JSMSG
ELSE

* Set UNC Path of Response XML file (Replace SYSTEMi with Server Name)
#BIF_RET10 := ‘\\SYSTEMi\UPS\Response’ + *JOBNBR + ‘.xml’

* Parse UPS Addres Validation Response XML
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser (‘load method(*file) file(/UPS/Response’ + *JOBNBR + ‘.xml)’)) TO_GET(#JSSTS #JSMSG)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘parse’) TO_GET(#JSSTS #JSMSG)

* Check Response Status Code (1=Success)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(Response/ResponseStatusCode)’) TO_GET(#JSSTS #JSMSG)
IF (#JSMSG = ‘1’)

* Check for NoCandidates Indicator, if present UPS did not find a valid matching address
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘check node(NoCandidatesIndicator)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS <> ‘EXIST’)

* Check for AmbiguousAddressIndicator, if present address is valid but matches multiple UPS addresses
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘check node(AmbiguousAddressIndicator)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS <> ‘EXIST’)

* Return Status as VA – Valid Address
#BIF_RET01 := ‘VA’
ELSE

* Return Status as AA – Ambiguous Address
#BIF_RET01 := ‘AA’
ENDIF

* Get Address Classification
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(AddressClassification/Code)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS = ‘OK’)

CASE (#JSMSG)

* UPS Classification 0: U – Unknown
WHEN (= ‘0’)
#BIF_RET02 := ‘U’

* UPS Classification 1: 1 – Commercial
WHEN (= ‘1’)
#BIF_RET02 := ‘C’

* UPS Classification 2: 2 – Residential
WHEN (= ‘2’)
#BIF_RET02 := ‘R’

ENDCASE

ELSE

* Return Classification as Unknown if Node not found
#BIF_RET02 := ‘U’
ENDIF

* Get UPS Suggested/Corrected Address
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(AddressKeyFormat/PoliticalDivision2)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS = ‘OK’)
#BIF_RET06 := #JSMSG
ENDIF
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(AddressKeyFormat/PoliticalDivision1)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS = ‘OK’)
#BIF_RET07 := #JSMSG
ENDIF
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(AddressKeyFormat/PostcodePrimaryLow)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS = ‘OK’)
#BIF_RET08 := #JSMSG
ENDIF
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(AddressKeyFormat/PostcodeExtendedLow)’) TO_GET(#JSSTS #JSMSG)
IF (#JSSTS = ‘OK’)
#BIF_RET09 := #JSMSG
ENDIF

* Address Lines are returned in multiple AddressLine nodes, loop to get Address1, Address2, and Address3
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘foreach node(AddressKeyFormat/AddressLine)’) TO_GET(#JSSTS #JSMSG)
BEGIN_LOOP
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘next object(*node)’) TO_GET(#JSSTS #JSMSG)
LEAVE IF(#JSSTS <> ‘OK’)
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(*current)’) TO_GET(#JSSTS #JSMSG)
IF (#BIF_RET03 = ”)
#BIF_RET03 := #JSMSG
ELSE
IF (#BIF_RET04 = ”)
#BIF_RET04 := #JSMSG
ELSE
IF (#BIF_RET05 = ”)
#BIF_RET05 := #JSMSG
ENDIF
ENDIF
ENDIF
END_LOOP
ELSE

* Return Status as NA – No Address Found
#BIF_RET01 := ‘NA’
ENDIF
ELSE
USE BUILTIN(JSMX_COMMAND) WITH_ARGS(#vParser ‘get node(Error/ErrorSeverity)’) TO_GET(#JSSTS #JSMSG)
IF (#JSMSG = ‘TransientError’)

* Return Status as UN – UPS HTTP service is unavailable
#BIF_RET01 := ‘UN’
#BIF_RET03 := ‘UPS Address Validation Service Unavailable’
ELSE

* Return Status as ER – Error occurred while validating
#BIF_RET01 := ‘ER’
#BIF_RET03 := ‘UPS Address Validation Service Error’
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF

ENDROUTINE

Lansa Composer Overview

I’m going to start a Composer section of this blog. I am calling this an overview because I wish to remain objective and I am not going to post my opinion of this product, if you would like to know my subjective opinion of this product feel free to contact me. I am not sure how many people are familiar with Lansa Composer as it is a relatively new product offering so I will start with a basic description of features. Lansa Composer provides a platform for data transport, transformation, and integration. Composer can be easily mastered by non-programming staff with the drag-and-drop Processing Sequences. Programmers have the ability to create custom activities to be used within processing sequences that can make us of Composers logging and notification functions. Lansa Composer’s base activities and transport configurations provide a visual front-end for Lansa Integrator. Maybe better to show with pictures:  

The Lansa Composer Welcome Screen, all navigation is located in the tree view on the left side of the screen. The Console displays the run history of Processing Sequences. The Console is an embedded WAM that can also be accessed remotely through the browser. This is handy when offsite to monitor and restart processes if necessary.

Composer maintains configurations for it’s builtin transport activities. Multiple configurations can be stored fot FTP, HTTP, SMTP, POP3, SMS, and messaging protocols. Additionally, stored configurations can be associated with a specific trading partner and settings can be dragged and dropped into a Processing Sequence.

Trading Partners represent both internal and external partners in the exchange of information. Trading partners may have associated configurations, transformation maps, and other details. Custom properties may be set up to hold specific information about a trading partner.

 Transformation maps are handled by Altova’s MapForce software integration. MapForce provides a drag and drop interface for mapping a data input source to an output source. Input and output sources can range from DB2 files to ODBC database connections as well as documents like CSV, XLS, XML, and EDI. The MapForce interface is easy to use but complicated logic can still be included as part of the data transformation though the use of conditions and functions. The example shown below is actually mapping an XML log from Veritas Backup Exec into a Lansa file which is then used to inform users of their PC backup status via email. 

 

The Processing Sequence is the backbone of Lansa Composer. It ties the configurations of trading partners, transport, activities, notifications, and logging all together. Actions, activities, and other data are dragged onto the processing sequence. Variables and Built-in constants can be dragged and dropped as parameters to activities. The processing sequence below is the “script” which processes the backup logs mentioned above. XML files are retrieved from servers in our home office and in a satellite office in Atlanta via FTP. Each XML file is then transformed as records in a Lansa database file. A Lansa function is then called to build notification emails which are sent by a separate processing sequence.