Tutorial 12

FAQ and Examples

 

Assumed Knowledge Level  

The following material assumes the reader has completed at least Tutorials 0 - 4.

FAQ – Configuration and Standards  

Should I give each developer their own Definition Set / Project Folder?

For training and experimentation purposes you might give each developer their own private definition set / project folder. When doing a real project you should never do this. Projects should be set up on a discrete project basis. Work done in a definition set / project folder cannot be merged with work done in another definition set / project folder. It is normal for multiple developers to be working on the same project with the same definition set.   

Can a screen's customization be completely removed?

Yes. Locate the file named Screen_xxxxxxxxxx.js in your definition set folder and delete it.

Before doing this end all aXes developer sessions.

Can a screen's customization be completely removed?

Yes. Locate the file named Screen_xxxxxxxxxx.js in your definition set folder and delete it.

Before doing this end all aXes developer sessions.

If I rename a screen, does AXES automatically remove the old screen file?

No, AXES will leave the old file intact. You will have to remove it manually.

 

FAQ – Scripting 

Can I create a script that runs when my application starts up and when the user has signed on?

Yes. See Application Level Basic Properties. Refer to the onApplicationStart, onApplicationEnd, onSignOn and onSignOff properties.

Can I create a script that runs when a particular screen arrives?

Yes. See axScreenBasicProperties - Screen Level Basic Properties. Refer to the onArrive and onLeave properties.  

How can I hide AXES menu bar and status bar?

SHOWAXESMENUBAR and SHOWAXESSTATUSBAR functions control the visibility of menu & status bar. If you want to always hide the menu / status in your application, put the following code in the onApplicationStart event of your application.

SHOWAXESMENUBAR(false); // false = hide, true = show
SHOWAXESSTATUSBAR(false);

My script needs to change a property of an element’s eXtension at runtime. Can it do that?

Yes, using the setProperty method of the element.

For example, a script under a certain condition needs to hide another element named "AdditionalDetails". We can do this by setting the visible property of the element to false (visible property can be found in the

property sheet under Basic Properties).

 

The script would look something like this:

 

// get a reference to the AdditionalDetails element
var F = FIELDS("AdditionalDetails");

// change the visible property to false
F.setProperty("visible", false);

// very important, must call refresh for property changes to take effect
F.refresh();

 

Note the last line – you need to call the refresh method of the element after changing a property of an element in order for the changes to be reflected visually.

Note how this is different from changing the current value of an element (remember that every element has a text value) using the setValue method – call to refresh is not required after setValue.

 

To get a property value, use the getProperty method.

Example:

If you wanted to show/hide according to its current state – if visible à hide otherwise show:

var F = FIELDS("AdditionalDetails");
var bVisible = F.getProperty("visible");

if (bVisible) {
  F.setProperty("visible", false);
} else {
  F.setProperty("visible", true);
}

F.refresh();
 

Note: you could write the above in only 3 lines of code:

var F = FIELDS("AdditionalDetails");
F.setProperty("visible",!(F.getProperty("visible")))
F.refresh();

For more detailed information about getProperty and setProperty methods refer to the Scripting Reference.

Can I stop the user pressing a function key?

Yes. Try this example as the onLeave script in a customized screen:

if (ENV.key == "F1") {
   var bOK = window.confirm("Press OK to send F1=Help request. Click Cancel to stop it."); 
   if (!bOK) ENV.returnValue = false;  
}
 

 When F1 is used this script confirms the usage - selectively cancelling it by setting ENV.returnValue = false;

 

FAQ – Customizing eXtensions

I want to always use the “Modern” look for all my group box eXtensions, can I do this without having to change the “look” property of each group box?

Yes. However, this feature is only available in TS Developer and TS2 Developer (TS2 Developer aXes 311.001 and latest releases).

When an eXtension is applied to a screen element, it is created based on a template. Every eXtension template is customizable – so what you need to do is to customize the group box template. Follow these steps to change the group box template’s “look” property to “modern”:

TS developer

1. Switch the aXes Designer’s view to Application Properties by clicking on the View Application Properties button at the bottom of the window.

2. Click on the Edit Application Properties button to switch to edit mode.

 

3. Click on the Edit Extensions button at the bottom of the window.


4. The Customize eXtensions window will appear, showing customizable eXtensions and a property sheet showing their current default values. Select Group Box in the drop down. Change the “look” property to “Modern”

 

 

5. Close this window. Now save the changes you made by clicking on the Save button at the top of the aXes Designer window. You should see a message indicating a successful save.

 

 

TS2 Developer

1.       Select Application from the Extendable Objects dropdown list in Developer and eXtensions tab. Click Edit Items button of the eXtensions property at Extension Template section.

 

 

2.       Edit Extensions Template window will appear. showing customizable eXtensions and a property sheet showing their current default values. Select Group Box in the drop down. Change the “look” property from ”Classic” to “Modern”. Click Ok button to close the window.

 

3.       Now click the Save button to save the change you made on Application properties.

 

 

Now whenever you apply the group box eXtension to a screen element, you will see that the “look” property is always set to “Modern” by default.

When I have modified a property in an eXtension template, can I override the property value in certain eXtension instances?

Yes you can. Remember that what you modify in the template is just a default value. You can always set a different value in your actual eXtension instances.

What else do I need to know about customizing eXtension templates?

-          Customize as early as possible before you start applying eXtensions to the screen elements.

-          Customizing templates instead of modifying properties of each eXtension instances will result in a smaller screen definition file (which means faster download from the server) as AXES only stores properties whose values are different from the default.

-          The “style” property of various eXtensions (e.g. button eXtensions) is a particularly good example of a property that should be set once only in the template as you would normally want all buttons to have the same style.

 

When I change the default “style” property of button eXtension, would this affect those buttons I created prior to this point?

Yes – as long as you haven’t modified the “style” property of those buttons. Remember that AXES physically stores only values that are different from their default values. So if you put various button eXtensions on your screen and you didn’t touch the “style” property of those buttons, any changes made to “style” property in the button template will immediately be reflected in all existing button instances.

 

FAQ – 5250 Popups

PopUp Windows and Screen Rendering in aXes-TS2

Over the years various 5250 pseudo window techniques have been utilized by 5250 developers. There are some important things you need to understand about them:

·          In no viable way are they really desktop windows - or even particularly viable substitutes for Windows desktop windows (in the sense of the full Windows API capabilities).

·          In new application design terms you should always view 5250 windows as the poor cousins of real Windows desktop windows (and even many other windows controls when used for field prompting). 

These 5250 pseudo windows come in three basic forms:                     

·          Imitations: the developer creates the appearance of a box using characters such as . : _ | (or a space with a background colour) to create a border. Recognising and dealing with windows of this type is not covered here.

·          DDS(-defined) windows: the developer uses the DDS WINDOW keyword to define the window and the operating system uses graphics characters to draw the window on the terminal.

·          Degraded DDS windows: the developer has defined the window in DDS but failed to leave space around it for the attribute bytes to turn graphics characters on/off. In this scenario, the operating system will draw it like a window imitation.

On most terminal emulators, degraded DDS windows look quite different to properly defined DDS windows so they should be easy to identify.

URL Popups Parameter

Until Version 2.1 aXes-TS2 treated every screen as a new screen and rendered it from scratch. 

In aXes-TS2 V2.1 a new parameter popups has been introduced to the aXes URL to identify and handle degraded DDS windows and to provide the option of keeping the previous screen background unchanged when a popup window is drawn over it.

Recognising Degraded DDS Windows

Until Version 2.1 aXes-TS2 did not recognise degraded DDS windows. This means they were not drawn in boxes with borders with shadows that make the windows stand out clearly. aXes was also unable to prevent user interaction with the background.

To address this issue the aXes-TS2 engine has been modified  to identify degraded DDS windows. It checks the details of the DDS record formats of the frontmost window sent by the aXesTS server, and if a record format of type “window” exists, but the screen contains no windows, aXes knows that a degraded DDS window exists and it can create one.

This behavior is enabled by default. The developer can use the URL popups parameter to instruct aXes to disable degraded DDS window detection:

http:\<host>\ts\ts2\index.html?popups=0

 

or

http:\<host>\ts\ts2\index.html?popups=2

 

See URL Parameters.

Limitations

If a degraded DDS window is drawn on top of another similar window, this technique cannot tell if it is a new window or the same window and will replace the currently rendered window. 

Degraded DDS windows cannot be identified in TELNET/PASSTHRU sessions because DDS level record format information is not available.

Keeping the Background Customizations

Prior to version 2.1, when a window is drawn over the current window, any customizations applied to the background window are lost because aXes redraws the screen from scratch and the background is redrawn as uncustomized. This is not a functional problem but the changes in the background window may be confusing to the user.

Therefore aXes-TS2 screen rendering has been modified so that instead of automatically redrawing the screen from scratch, it can check if the virtual device has any windows. It compares the number found with the number currently rendered, deletes any excess (i.e. if the previous screen had a window now closed) and then redraws the topmost window. This way, the background along with any customizations remains intact. 

In the majority of situations the background should be kept. This is the default behavior. However, to allow for special circumstances the developer can use the popups parameter to instruct aXes to not keep the background:

http:\<host>\ts\ts2\index.html?popups=0

 

or

http:\<host>\ts\ts2\index.html?popups=1

 

It should be noted that when the screen background is kept, there is a possibility of screen level eXtensions interfering with each other in unexpected ways. A window is seen as a new screen and, when customized, gets its own set of screen eXtensions. Where extensions were previously applied to a “clean slate”, they are now being applied on top of the extensions from one or more previous screens. This may produce some unexpected results.

See URL Parameters.

When I start customizing a popup screen, it completely covers the whole screen, is this normal?

Yes. Popup screens will only show the previous screen in the area surrounding the popup if the popup screen is uncustomized. Once you start to customize a popup screen, it will cover up the surrounding area.

I have customized a screen (screen A) that can invoke a popup (screen B). When the popup appears, what was shown in the background is the original uncustomized screen A instead of the customized one. What should I do?

 

From version 2.1.11 onwards aXes-TS2 will keep the background customizations. Check that this has not been turned off with the popups parameter of the axes URL. See Keeping the Background Customizations.

 

In aXes-TS1 make the popup screen (screen B) into a customized screen - even when you don’t change anything in the popup screen.

See the FAQ item above – when a popup screen (screen B) is customized, it will completely cover the whole screen & the previous screen (screen A) will not show in the background anymore.

My popup is making use of the cursor to allow the user to select an item from a list, how will this work after the screen has been customized as the cursor does not show anymore?

If your popup allows the user to select a value by displaying a list of values, and the user selects one by putting the cursor on the list entry and pressing enter, then the best alternative is to customize the popup screen and add a hyperlink extension to the first field in the popup's list, as explained in the section above (Example – Working with 5250 Cursor).

You could also enable the limitedCursorSupport option on the popup screen, though visually, the first option is much better. Note that the limited cursor support is meant as a temporary measure only and should not be used as a permanent solution.

If you are enabling the limitedCursorSupport, make sure that you set the keepPopupLocation property of the popup screen to true.

PopupWindow Extension

You can use the axPopupWindow  extension to customize the position, size and style of a 5250 window in TS2. You may want to use the extension for example when you have a customised background that has rearranged the screen and you also need to move the initial position of popups.

 

 

FAQ – Calling IBM i Server programs from eXtension scripts  

The TABLEMANAGER object provided by aXes allows you to call RPG or CL programs directly from eXtension JavaScript.  The programs that you call need to be thread safe. See the end of this section for more details.  Typically the calls are made when a button or hyperlink is clicked.    

Example 1 – Call with no parameters

Server CL program

This small CL program named LOTST009 sends a message to the system history log:

 

PGM                                               

SNDPGMMSG  MSG('Hello from LOTST009') TOMSGQ(QHST)

ENDPGM          

                                 

Button onClick JavaScript coding in Axes

 

TABLEMANAGER.callProgram("LOTST009","QGPL");

Result

Display the system history log (DSPLOG command) and locate message "Hello from LOTST009".

Example 2 – Call with one parameter

Server CL  program

This small CL program named LOTST011 sends a message to the system history log. The message text is passed into the program as a parameter. 

 

PGM (&MSG)                         

   DCL &MSG *CHAR 132              

   SNDPGMMSG  MSG(&MSG) TOMSGQ(QHST)

ENDPGM                             

 

Button onClick JavaScript coding in Axes

 

TABLEMANAGER.callProgram("LOTST011","QGPL",

             {type:"alpha",len:132,value:"This is a test message"}  );

 

Result

Display the system history log (DSPLOG command) and locate message "This is a test message".

Example 3 – Call with a returned value

Server CL program

This small CL program named LOTST001 concatenates two alpha parameters and returns the result. 

 

PGM (&P1 &P2 &P3)        

   DCL &P1 *CHAR 10         

   DCL &P2 *CHAR 10         

   DCL &P3 *CHAR 20         

   CHGVAR &P3 (&P1 *CAT &P2)

ENDPGM                   

 

Button onClick JavaScript coding in Axes

 

var result = TABLEMANAGER.callProgram("LOTST001","QGPL",

                          {type:"alpha",len:10,value:"Hello"},

                          {type:"alpha",len:10,value:"World"},

                          {type:"alpha",len:20,pass:false,ret:true} );

 

if (result.error) alert("Call Failed");

else alert(result.returnParms[3]);

 

Result

 

Example 4 – Numeric Parameters passed and returned

Server CL program

This small CL program named LOTST002 adds up 3 packed decimal parameters and returns the result. 

 

PGM (&P1 &P2 &P3 &P4)      

DCL &P1 *DEC (7 0)         

DCL &P2 *DEC (9 2)         

DCL &P3 *DEC (12 5)        

DCL &P4 *DEC (15 5)        

CHGVAR &P4 (&P1 + &P2 + &P3)

ENDPGM 

                   

Button onClick JavaScript coding in Axes

var result = TABLEMANAGER.callProgram("LOTST002","QGPL",

                  { type:"packed",len:7,dec:0,value: 4 },

                  { type:"packed",len:9,dec:2,value: 7.7 },

                  { type:"packed",len:12,dec:5,value: 820.12345 },

                  { type:"packed",len:15,dec:5,pass:false,ret:true } );

 

if (result.error) alert("Call Failed");

else

{

  var strp4   = result.returnParms[4];         /* Should be 831.82345 as string */

  var intp4   = parseInt(strp4,10) + 42;       /* Should be 831 + 42 = 873 */

  var floatp4 = parseFloat(strp4) + 123.75868; /* Should be 955.58213 as float */ 

  alert("strp4=" + strp4 + ", intp4=" + intp4.toString() + ", floatp4=" + floatp4.toString());

}

 

Result

Note that the returned parameter (strP4) is a always a string. By using the standard JavaScript functions parseInt() and parseFloat() it can easily be converted to a number for further manipulation by JavaScript code.

Example 5 – Value Passed and Returned in same Parm

Server CL program

This small CL program named LOTST005 sends a message to the system operator and returns "OKAY":  

PGM (&P1)                         

DCL &P1 *CHAR 20                  

SNDMSG     MSG(&P1) TOMSGQ(QSYSOPR)

CHGVAR &P1 'OKAY'                 

ENDPGM                            

 

Button onClick JavaScript coding in Axes

 

var message = "Hello from LOTST005";

var result  = TABLEMANAGER.callProgram("LOTST005","QGPL",

                 {type:"alpha",len:20,pass:true,ret:true,value:message} );

 

if (result.error) alert("Call Failed");

else alert(result.returnParms[1]);

 

Result

 

 

Example 6 – Multiple Values Passed and Returned

Server CL program

This small CL program named LOTST007 receives and returns multiple parameters:

PGM (&P1 &P2 &P3 &P4)   

DCL &P1 *CHAR 10        

DCL &P2 *CHAR 10        

DCL &P3 *CHAR 20        

DCL &P4 *CHAR 10        

CHGVAR &P3 (&P1 *CAT &P2)

CHGVAR &P4 'OKAY'       

ENDPGM                  

 

Button onClick JavaScript coding in Axes

var p1 = "JavaScript";

var p2 = "is Good";

var result  = TABLEMANAGER.callProgram("LOTST007","QGPL",

                 { type:"alpha",len:10,pass:true,value:p1  },

                 { type:"alpha",len:10,pass:true,value:p2  },

                 { type:"alpha",len:20,pass:false,ret:true },

                 { type:"alpha",len:10,pass:false,ret:true } );

if (result.error) alert("Call Failed");

else

{

   var p3 = result.returnParms[3];

   var p4 = result.returnParms[4];

   alert("p3=" + p3 + ", p4=" + p4);

}

 

Result

 

Example 7 – Multiple Values Returned in One Parameter

Server CL program

This small CL program named LOTST010 returns a 2000 byte string containing the aXes server's job name, user profile, job number and current output queue. The returned information is formatted as a JSON string. This program is the tip of a very large iceberg of possibility.

 

PGM (&JSON)                                                

   DCL &JSON *CHAR 2000                                    

 

   DCL &JOB    *CHAR 10                                    

   DCL &USER   *CHAR 10                                     

   DCL &JOBNBR *CHAR 6                                     

   DCL &OUTQ   *CHAR 10                                    

                                                           

   RTVJOBA JOB(&JOB) USER(&USER) NBR(&JOBNBR) OUTQ(&OUTQ)

                                                           

   CHGVAR &JSON (' JOB:"' || &JOB |< '"')                  

   CHGVAR &JSON (&JSON |< ',JOBNBR:"' || &JOBNBR |< '"')   

   CHGVAR &JSON (&JSON |< ',USER:"' || &USER |< '"')       

   CHGVAR &JSON (&JSON |< ',OUTQ:"' || &OUTQ |< '"')       

                                                           

ENDPGM      

 

Button onClick JavaScript coding in Axes – Stage 1

 

var result = TABLEMANAGER.callProgram("LOTST010","QGPL",{type:"alpha",len:2000,pass:false,ret:true});

alert( result.returnParms[1] );

 

Result – Stage 1

 

 

The CL program LOTST010 is executed and it returns a long string with the job name, job number, user and output queue all imbedded in it.

 

In effect LOTST010 has returned 4 values, but done it using a single program parameter. 

Note how the returned string is formatted in much the same way as the program arguments are specified in JavaScript - type:"alpha",len:2000,pass:false,ret:true

 

Button onClick JavaScript coding in Axes – Stage 2

 

var result = TABLEMANAGER.callProgram("LOTST010","QGPL",{type:"alpha",len:2000,pass:false,ret:true});

try

{

   var Info = eval( "({" + result.returnParms[1] + "})" )

   window.alert("Job=" + Info.JOB + ", Job Number=" + Info.JOBNBR + ", User=" + Info.USER + ",Output Queue=" + Info.OUTQ); 

}

catch (oe)

{

   window.alert( "Error " + oe.description + " detected when loading JSON data " + result.returnParms[1] );

}

 

Result – Stage 2

 

 

The CL program LOTST010 is executed and it returns a long string with the job name, job number, user and output queue all imbedded in it.

 

The value returned is in JSON string format.

 

The returned value is executed by using a JavaScript eval operation to create a JavaScript object named Info.

 

Now the properties Info.JOBInfo.JOBNBR, ,  Info.USER and  Info.OUTQ are all accessible to the JavaScript code. In effect the 4 values returned by CL program LOTST010 are now individually accessible as JavaScript object properties. 

 

The JSON interface format is a driving part of the Web 2.0 / AJAX technologies. Some of the reasons it is very useful include:

 

·         You can alter CL program LOTST010 to return more values at any time. This would not upset any existing JavaScript calls to LOTST010. That would not be true if you were using traditional parameters – every caller would need to change.

 

·         Your JavaScript can easily check whether the version of LOTST010 that it called has actually returned a property by coding, say,

var UseJobSize = 42; /* Set the default value this thing */

if (Info.JOBSIZE != null) UseJobSize = Info.JOBSIZE; /* Server value provided */ 

 

to decide whether LOTST010 returned a value named JOBSIZE.

 

·         The JSON string you return can be much more complex in nature and include lists, arrays, structures, etc. All of these are instantly accessible to your JavaScript code.  

Usage Rules, Guidelines and Tips

The server side program calls are not performed in the same job as the 5250 session. They are initiated from within the aXes server job.

 

The calls are performed under the user profile that the aXes server jobs run under – not under the user profile who started the 5250 session. However, you can call a CL program that submits jobs for execution under 5250 users profile. See the IBM i SBMJOB command and the concept of job descriptions.  

 

The calls are performed in a multi-threaded  process. This means that all resources are shared with all the other active threads - which may also be executing concurrent call operations.

 

Some of the things that this means include:

You have only 1 QTEMP library that is shared by all threads in the process (ie: IBM i server job).

 

The IBM i library list concept cannot be practically used. If you change the job's library list then  a millisecond later another thread may alter it to something else. You have to use library qualified reference to most IBM i objects.  Implement a design such that the client USERENV object knows the library name(s) associated with the 5250 user and pass them to the server program(s) as parameters so that all object references are fully qualified.  

Your CL and RPG programs need to be compiled to be thread safe.   Generally programs should be compiled using the CRTBNDCL or CRTBNDRPG commands.  RPG programs probably need to use the THREAD(*SERIALIZE) control specification option. You should also review all IBM supplied documentation for executing multi-threaded RPG and CL programs before using this aXes feature.       

Your CL and RPG programs cannot open a 5250 display file because they are not executing in a  context where they are associated with a 5250 device.   

Your CL and RPG programs need to be robust. They need to trap errors and handle them gracefully releasing all open or allocated resources. 

 

Your CL and RPG programs need to be symmetrical in resource usage. Basically this means that if something is opened, locked or allocated as the program is executing it always it needs to be symmetrically closed, unlocked or de-allocated as the program is terminating.

Your server programs need to be stateless between each call. This means they must terminate (set on LR in RPG terminology) at the end of every call and cannot remember values between calls in their variables nor leave any resource open or allocated. Where a stateful design is required, typically a unique token or some sort of session id is assigned by the server that can be used to save and restore state on each call.    

 

Your CL and RPG programs need to execute quickly. Anything that would take more than 1 or 2 seconds to execute should be submitted to batch instead.

 

If your 5250 session locks up you probably have poor error handling in your program. It has failed and is waiting for the system operator to reply to a message. It is recommended that you improve such default IBM i error handling.

 

Your RPG or CL program must never ever change the execution characteristics of the IBM i job they are executing. Job characteristics include anything that may impact other programs executing within the same job – including things like library lists, priorities, activation groups, CCSIDs, contents of QTEMP, etc.

 

FAQ – Developer Mode Issues

aXes Designer has become very sluggish, is there anything I can do?

After using aXes in developer mode for a while, you might feel that it has become slow and heavy. This could be caused by the Internet Explorer memory leaking. Unfortunately there is no solution to this problem and the only way to get around this is by restarting your browser completely when you are experiencing this problem.

Please make sure that you completely close your browser window (as opposed to just reloading aXes).

 

I’m getting an error message “System call failed” while attempting to perform an operation in the designer. What should I do?

 

 

If you are in the middle of editing, save your work immediately.

Then close your browser window and reopen it.

 

Example – Using a Hyperlink to select a subfile entry 

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Select is used.

The subfile selection column is field named Sel and the employee number column is named Employee:  

 

 

Result

The selection column is still visible, but an employee can be selected and displayed by clicking on the employee number hyperlink:

 

 

Steps

 

v  The employee column was selected and changed to be a Hyperlink.

 

v  These properties were then set in the hyperlink eXtension:

 

caption (script):

 

ENV.returnValue = FIELD.getValue();

 

style

 

 

onClick (script)

 

/* Find out the subfile index of this field (the one clicked on) */

 

var iSFLIndex = FIELD.getIndex();

 

/* Get a reference to the "Sel" field with the same subfile index */

 

var oSel = FIELDS("Sel",iSFLIndex);

 

/* If found, set the "Sel" field to "X" and press Enter */

 

if (oSel != null)

{

   oSel.setValue("X");

   SENDKEY("Enter");   

}

 

mouseOverColor

 

blue

 

 

 

Example – Iterating through subfiles entries 

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Select is used.

The subfile selection column field is named Sel, and the employee number, Surname and Given Name columns are named Employee, Surname and Given_Name respectively:  

 

 

Result

A copy to clipboard push button has been added. When clicked all the subfile entries on the current screen are copied to the clipboard: 

 

 

Steps

 

v  A new element was added to the screen as a push button.

 

v  These properties were then set in the push button eXtension:

 

caption (simple text):

 

Copy Subfile to Clipboard

 

onClick (script)

 

var sClipData = "";

var sTab      = "\t";

 

var iSFLIndex = 1;

var oEmployee = FIELDS("Employee",iSFLIndex);

while (oEmployee != null)

{

    var oSurname   = FIELDS("Surname",iSFLIndex);

    var oGivenName = FIELDS("Given_Name",iSFLIndex);

    sClipData += oEmployee.getValue() + sTab;

 

    if (oSurname != null) sClipData += oSurname.getValue()   + sTab; 

    else sClipData += "UNKNOWN" + sTab;  

 

    if (oGivenName != null) sClipData += oGivenName.getValue() + sTab;

    else            sClipData += "UNKNOWN" + sTab;

 

    sClipData += "\n";

    iSFLIndex += 1;

    oEmployee = FIELDS("Employee",iSFLIndex);

window.clipboardData.clearData();

window.clipboardData.setData("Text",sClipData);

 

Observations

The employee number field was chosen to drive the subfile iteration loop because it exists for every subfile entry.

 

The subfile iteration loop is structured like this:

 

var iSFLIndex = 1;

var oEmployee = FIELDS("Employee",iSFLIndex);

while (oEmployee != null)

{

   

    << Process employee >>

   

    iSFLIndex += 1;

    oEmployee = FIELDS("Employee",iSFLIndex);

 

The loop starts at entry one and proceeds until an employee number cannot be found in the subfile.

 

When iterating a subfile the possibility exists that blank outfield entry will not actually exist on the 5250 display. That is why the logic     

 

    var oSurname = FIELDS("Surname",iSFLIndex);

 

    if (oSurname != null) sClipData += oSurname.getValue()   + sTab; 

    else                  sClipData += "UNKNOWN" + sTab;  

 

is used. It caters for the fact that an employee's surname may be blank and therefore the entry does not exist in the subfile. 

 

The clipboard data is tab delimited so it can be pasted as columns to, for example, MS-Excel.

 

You would probably not code logic like this in a real application. It would be better to create a generic subfile clipboard function in your USERENV object and simply call it from the onClick routine. 

 

Example – USERENV and Generic Coding   

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Select is used.

The subfile selection column field is named Sel, and the employee number, Surname, Given Name and Date of Birth columns are named Employee, Surname and Given_Name and Date_of_Birth respectively:  

 

 

Result

A copy to clipboard push button has been added. When clicked all the subfile entries on the current screen are copied to the clipboard. The copy is achieved by invoking a generic function defined in the USERENV object: 

 

 

Steps

 

v  This generic function was added to the USERENV object (this code is a generic version of the code using in the preceding subfile and clipboard example):  

 

   sendSubfiletoClipBoard : function(oP)  

   {

      var iSFLIndex  = 0;

      var iLimit     = oP.sendFields.length;

      var sClipData  = "";

      var sTab       = "\t";

      var sNL        = "\n";

      var oiterField = null;

 

      iSFLIndex = 1;

      oiterField = oP.ENV.FIELDS(oP.iterField,iSFLIndex);

      while (oiterField != null)

      {

         for (var i = 0; i < iLimit; i++)

         {

            var osendField = oP.ENV.FIELDS(oP.sendFields[i],iSFLIndex);

            if (osendField != null) sClipData += osendField.getValue()   + sTab; 

            else                    sClipData += oP.notFound + sTab;  

         }   

         sClipData += sNL;

         iSFLIndex += 1;

         oiterField  = oP.ENV.FIELDS(oP.iterField,iSFLIndex);

      }  

 

      window.clipboardData.clearData();

      if (sClipData != "") window.clipboardData.setData("Text",sClipData);          

   

   }, /* <= Note the comma */

 

 

v  A new aXes developer browser session was started to ensure that the updated USERENV code is loaded.

 

v  A new element was added to the XHRRPGTRN_Select screen as a push button.

 

v  These properties were then set in the push button eXtension:

 

caption (simple text):

 

Copy Subfile to Clipboard

 

onClick (script)

 

var oP = { ENV       : ENV,

           iterField : "Employee",

           notFound  : "Not found",

           sendFields: ["Employee","Surname","Given_Name","Date_of_Birth"] }; 

 

USERENV.sendSubfiletoClipBoard(oP);

 

Observations

The USERENV function sendSubfiletoClipBoard receives an object as a parameter.

The properties within the object parameter are:

v  ENV: a reference to the calling scripts ENV execution environment. This allows the generic code to access the callers standard functions, etc.

v  iterField: The name of the subfile fields to use to iterate the subfile.

v  notFound: The text to be output to the clipboard for a field not found in the subfile. 

v  sendFields: An array of the names of the subfile fields to be copied to the clipboard. 

 

Using a generic routine like this in USERENV means that it is easier to add a "Copy to Clipboard" button to lots of screens and avoid repeating JavaScript code.

 

Example – Selectively Iterating through subfiles entries 

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Select is used.

The subfile selection column field is named Sel, and the employee number, Surname and Given Name columns are named Employee, Surname and Given_Name respectively:  

 

 

Result

A copy to clipboard push button has been added. When clicked subfile entries selected with a "C" on the current screen are copied to the clipboard: 

 

 

Steps

 

v  A new element was added to the screen as a push button.

 

v  These properties were then set in the push button eXtension:

 

caption (simple text):

 

Copy Subfile to Clipboard

 

onClick (script)

 

var sClipData = "";

var sTab      = "\t";

 

var iSFLIndex = 1;

var oSel = FIELDS("Sel",iSFLIndex);

while (oSel != null)

{

    if (oSel.getValue() == "C")

    {

       var oEmployee  = FIELDS("Employee",iSFLIndex);

       var oSurname   = FIELDS("Surname",iSFLIndex);

       var oGivenName = FIELDS("Given_Name",iSFLIndex);

       oSel.setValue(" ");    

       sClipData += oEmployee.getValue() + sTab;

       if (oSurname != null) sClipData += oSurname.getValue()   + sTab; 

       else sClipData += "UNKNOWN" + sTab;   

       if (oGivenName != null) sClipData += oGivenName.getValue() + sTab;

       else            sClipData += "UNKNOWN" + sTab;

       sClipData += "\n";

    }

 

    iSFLIndex += 1;

    oSel = FIELDS("Sel",iSFLIndex);

 

if (sClipData == "")

{

  window.alert("No employees were selected to copy. Put a C beside the employees to copy."); 

}

else

{

   window.clipboardData.clearData();

   window.clipboardData.setData("Text",sClipData);

}

 

Observations

The selection ("Sel") field was chosen to drive the subfile iteration loop because it exists for every subfile entry as it is an input field. The subfile iteration loop is structured like this:

 

var iSFLIndex = 1;

var oSel = FIELDS("Sel",iSFLIndex);

while (oSel != null)

{

    if (oSel.getValue() == "C")

    {

        << Process selected subfile Entry >>

    }

 

    iSFLIndex += 1;

    oSel = FIELDS("Sel",iSFLIndex);

 

The loop starts at entry one and proceeds until no selection field can be found. Only entries selected with a "C" are processed.

 

This line removes the "C" from a selected line:

 

oSel.setValue(" ");    

 

When iterating a subfile, the possibility exists that a blank outfield entry will not actually exist on the 5250 display. That is why the logic     

 

    var oSurname = FIELDS("Surname",iSFLIndex);

 

    if (oSurname != null) sClipData += oSurname.getValue()   + sTab; 

    else                  sClipData += "UNKNOWN" + sTab;  

 

is used. It caters for the fact that an employee's surname may be blank and therefore the entry does not exist in the subfile. 

 

The clipboard data is tab delimited. This makes it nice to paste into MS-Excel.

 

You would probably not code logic like this in a real application. It would be better to create a generic subfile clipboard function in your USERENV object and simply call it from the onClick routine. 

 

 

Example - Hiding and Showing Fields on a Customized Screens

 

The starting point for this example is the System i Main Menu, where:

v  The screen is named MAIN

v  The selection or command field is named CommandLine.

v  Two push button eXtensions, captioned Show and Hide, have been added.

 

 

The onClick property of the Show push button is set to this:

 

var cmdfld= FIELDS("CommandLine");

 

cmdfld.setProperty("visible",true);

 

cmdfld.refresh();

 

The onClick property of the Hide push button is set to this:

 

var cmdfld= FIELDS("CommandLine");

 

cmdfld.setProperty("visible",false);

 

cmdfld.refresh();

 

 

When clicked they cause the selection or command field (CommandLine) to appear and disappear.

 

You can minimize this type of scripting by adding two generic functions like this to your USERENV object:

 

   showField : function(env,name,index)

   {

     var oField = env.FIELDS(name,index);

     if (oField != null)

      {

         oField.setProperty("visible",true);

         oField.refresh();

      }

   },

 

   hideField : function(env,name,index)

   {

      var oField = env.FIELDS(name,index);

      if (oField != null)

      {

         oField.setProperty("visible",false);

         oField.refresh();

      }

   },

 

Now the onClick property of the Show push button may be reduced to this:

 

USERENV.showField(ENV,"CommandLine");

 

and the onClick property of the Hide push button reduced to this:

 

USERENV.hideField(ENV,"CommandLine");

 

You now have generic hide and show functions you can use on any field.

 

Some other notes about this example:

 

·         The ENV parameter passed into the hide and show functions as a way that it can operate in your scripts context/environment. For example, by passing ENV to the functions they can use the ENV.FIELDS() function.

 

·         The optional index parameter used by the show and hide functions is for when you are dealing with subfile fields. Pass it as the index of the subfile field.    

 

·         Don't forget to close and restart your aXes development session to pick up USERENV object definition changes.  

 

 

 

Example – Dynamic Styling 

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Select is used.

 

 

Result

The style of the Date of Birth Column is changed dynamically to highlight people born in the 50s, 60s, and 70s. Dynamic styling is often used to draw attention to special situations:   

 

 

Note: The color scheme here is only being used to demonstrate a technique.    

Steps

 

v  The Date of Birth element was selected and these properties were set in its Default Visualization eXtension:

 

style (as script)

 

var sDecade = FIELD.getValue().charAt(6);

 

var oStyle  = null;

 

switch (sDecade)

{

   case "5": oStyle = {"color":"red",   "background":"black"};  break;

   case "6": oStyle = {"color":"orange","background":"white"};  break;

   case "7": oStyle = {"color":"white", "background":"orange"}; break;

   default : oStyle = {"color":"blue"}; break;

}

 

ENV.returnValue = oStyle;

 

Observations

 

Dynamically evaluated styles need to return a JavaScript object containing the style property name and its value.

The preceding code does this by dynamically manufacturing an object containing the required style properties.

 

If this code was added to the USERENV object:

 

/* ----------------------------------------------------------------------

/* The USERENV object contains all customer defined shared scripts and

/* ----------------------------------------------------------------------

   

var USERENV =

{

   o50DecadeStyle  : {"color":"red",   "background":"black"},

   o60DecadeStyle  : {"color":"orange","background":"white"},

   o70DecadeStyle  : {"color":"white", "background":"orange"},

   oDftDecadeStyle : {"color":"blue"},

 

and the preceding dynamic styling code was changed to this:

 

var sDecade = FIELD.getValue().charAt(6);

switch (sDecade)

{

   case "5": ENV.returnValue = USERENV.o50DecadeStyle; break;

   case "6": ENV.returnValue = USERENV.o60DecadeStyle; break;

   case "7": ENV.returnValue = USERENV.o70DecadeStyle; break;

   default : ENV.returnValue = USERENV.oDftDecadeStyle; break;;

}

 

then the result would be improved for two reasons:

 

v  The styles have been externalized for a single point of change.

 

v  The code is considerably more efficient because the returned styles objects do not have to be repeatedly created. Only the 4 decade styles in USERENV ever have to be created.   

 

Note: If you try this approach out - remember that you have to signoff, then close and reopen the browser window for any changes to USERENV to be loaded. USERENV is only loaded as the aXes terminal session is started.

 

 

Example – Dynamically refreshing a drop down without server interaction 

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Maint is used.

The State field is named Employee_State and the Country field is Employee_Country:  

 

 

Result

Employee_State and Employee_Country are replaced with drop downs that source its data from static tables. Employee_State is sensitive to the value of Employee_Country so that it will show the states for the selected country without server interaction:

 

 

Steps

 

Edit tables_static.txt and add the following table of US and Australian states:

DefineObjectInstance {

 

  className = "StaticTable",

  name    = "US_OZ_State", 

  source  = "inline", 

  rows = {

       {value="AK",text="Alaska", countryCode="USA"},

       {value="AL",text="Alabama", countryCode="USA"},

       {value="AR",text="Arkansas", countryCode="USA"},

       {value="AS",text="American Samoa", countryCode="USA"},

       {value="AZ",text="Arizona", countryCode="USA"},

       {value="CA",text="California", countryCode="USA"},

       {value="CO",text="Colorado", countryCode="USA"},

       {value="CT",text="Connecticut", countryCode="USA"},

       {value="DC",text="District of Columbia", countryCode="USA"},

       {value="DE",text="Delaware", countryCode="USA"},

       {value="FL",text="Florida", countryCode="USA"},

       {value="GA",text="Georgia", countryCode="USA"},

       {value="GU",text="Guam", countryCode="USA"},

       {value="HI",text="Hawaii", countryCode="USA"},

       {value="IA",text="Iowa", countryCode="USA"},

       {value="ID",text="Idaho", countryCode="USA"},

       {value="IL",text="Illinois", countryCode="USA"},

       {value="IN",text="Indiana", countryCode="USA"},

       {value="KS",text="Kansas", countryCode="USA"},

       {value="KY",text="Kentucky", countryCode="USA"},

       {value="LA",text="Louisiana", countryCode="USA"},

       {value="MA",text="Massachusetts", countryCode="USA"},

       {value="MD",text="Maryland", countryCode="USA"},

       {value="ME",text="Maine", countryCode="USA"},

       {value="MI",text="Michigan", countryCode="USA"},

       {value="MN",text="Minnesota", countryCode="USA"},

       {value="MO",text="Missouri", countryCode="USA"},

       {value="MP",text="Northern Mariana Islands", countryCode="USA"},

       {value="MS",text="Mississippi", countryCode="USA"},

       {value="MT",text="Montana", countryCode="USA"},

       {value="NC",text="North Carolina", countryCode="USA"},

       {value="ND",text="North Dakota ", countryCode="USA"},

       {value="NE",text="Nebraska", countryCode="USA"},

       {value="NH",text="New Hampshire", countryCode="USA"},

       {value="NJ",text="New Jersey", countryCode="USA"},

       {value="NM",text="New Mexico", countryCode="USA"},

       {value="NV",text="Nevada", countryCode="USA"},

       {value="NY",text="New York", countryCode="USA"},

       {value="OH",text="Ohio", countryCode="USA"},

       {value="OK",text="Oklahoma", countryCode="USA"},

       {value="OR",text="Oregon", countryCode="USA"},

       {value="PA",text="Pennsylvania ", countryCode="USA"},

       {value="PR",text="Puerto Rico", countryCode="USA"},

       {value="RI",text="Rhode Island ", countryCode="USA"},

       {value="SC",text="South Carolina", countryCode="USA"},

       {value="SD",text="South Dakota ", countryCode="USA"},

       {value="TN",text="Tennessee", countryCode="USA"},

       {value="TX",text="Texas", countryCode="USA"},

       {value="UM",text="United States Minor Outlying Islands", countryCode="USA"},

       {value="UT",text="Utah", countryCode="USA"},

       {value="VA",text="Virginia", countryCode="USA"},

       {value="VI",text="Virgin Islands", countryCode="USA"},

       {value="VT",text="Vermont", countryCode="USA"},

       {value="WA",text="Washington", countryCode="USA"},

       {value="WI",text="Wisconsin", countryCode="USA"},

       {value="WV",text="West Virginia", countryCode="USA"},

       {value="WY",text="Wyoming", countryCode="USA"},

       {value="ACT",text="Canberra", countryCode="AUS"},

       {value="NSW",text="New South Wales", countryCode="AUS"},

       {value="QLD",text="Queensland", countryCode="AUS"},

       {value="NT",text="Northern Territory", countryCode="AUS"},

       {value="SA",text="South Australia", countryCode="AUS"},

       {value="VIC",text="Victoria", countryCode="AUS"},

       {value="WA",text="Western Australia", countryCode="AUS"},

       {value="TAS",text="Tasmania", countryCode="AUS"},

     }, 

};

 

v  Employee_State was selected and changed to be a Drop Down.

 

v  These properties were then set in the Drop Down eXtension:

 

dataSourceType:

 

     Static Table

 

     tableName:

 

     US_OZ_State

 

onFillDropDown (script):

 

/* Get the current country */

var currentCountry = FIELDS("Employee_Country").getValue();

 

/* Only add entries to the drop down which match the value of the country field */

if (currentCountry == ROW.countryCode)

{

   ENV.returnValue = ROW.text;

}

else

{

   ENV.returnValue = null;

}

·               Employee_Country was selected and changed to be a Drop Down

·               These properties were then set in the Drop Down eXtension:

dataSourceType:

 

     Static Table

 

     tableName:

 

     ISOCountry

 

onFillDropDown (script):

 

/* For the purpose of this exercise, filter the countries so that we only add Australia and USA */

var country = ROW.value;

 

if ( (country == "AUS" ) || (country == "USA" ) )

{

  ENV.returnValue = ROW.text;

}

else

{

  ENV.returnValue = null;

}

 

onSelectValueChanged (script):

 

/* Set the country field value first and then refresh the state drop down. Refreshing the state drop down will cause it to be reloaded from the table data triggering the onFillDropDown event. Because now the country has changed, the state drop down will only fill the states according to the changed value of country */

FIELD.setValue(ROW.value);

FIELDS("Employee_State").refresh();

 

 


 

Remarks:

 

To avoid the Drop Down overlapping part of the input fields you can set the font size in the Drop Down's style property. For example, you can set it to xx-small:

 

 

In this picture we changed the size of the States but not the Country:

 

Example – Two level drop down 

Start Point

The shipped aXes demonstration screen named XHRRPGTRN_Maint is used.

The State field is Employee_State:  

 

 

Result

States appear as children of the Country:

 

 

 

Steps

 

Use the same table as the one used for the Dynamically refreshing a drop down without server interaction example.

Edit static_tables.txt and add two rows:

 

One before the first row containing the Australian States and another one before the first row of US States:

 

 


       {optgroup="Australian States"},

       {value="ACT",text="Canberra", countryCode="AUS"},

       {value="NSW",text="New South Wales", countryCode="AUS"},

       {value="QLD",text="Queensland", countryCode="AUS"},

       {value="NT",text="Northern Territory", countryCode="AUS"},

       {value="SA",text="South Australia", countryCode="AUS"},

       {value="VIC",text="Victoria", countryCode="AUS"},

       {value="WA",text="Western Australia", countryCode="AUS"},

       {value="TAS",text="Tasmania", countryCode="AUS"},

       {optgroup="United States"},

       {value="AK",text="Alaska", countryCode="USA"},

       {value="AL",text="Alabama", countryCode="USA"},

       {value="AR",text="Arkansas", countryCode="USA"},

       {value="AS",text="American Samoa", countryCode="USA"},

       {value="AZ",text="Arizona", countryCode="USA"},

       {value="CA",text="California", countryCode="USA"},

       {value="CO",text="Colorado", countryCode="USA"},

       {value="CT",text="Connecticut", countryCode="USA"},

       {value="DC",text="District of Columbia", countryCode="USA"},

       {value="DE",text="Delaware", countryCode="USA"},

       {value="FL",text="Florida", countryCode="USA"},

       {value="GA",text="Georgia", countryCode="USA"},

       {value="GU",text="Guam", countryCode="USA"},

       {value="HI",text="Hawaii", countryCode="USA"},

       {value="IA",text="Iowa", countryCode="USA"},

       {value="ID",text="Idaho", countryCode="USA"},

       {value="IL",text="Illinois", countryCode="USA"},

       {value="IN",text="Indiana", countryCode="USA"},

       {value="KS",text="Kansas", countryCode="USA"},

       {value="KY",text="Kentucky", countryCode="USA"},

       {value="LA",text="Louisiana", countryCode="USA"},

       {value="MA",text="Massachusetts", countryCode="USA"},

       {value="MD",text="Maryland", countryCode="USA"},

       {value="ME",text="Maine", countryCode="USA"},

       {value="MI",text="Michigan", countryCode="USA"},

       {value="MN",text="Minnesota", countryCode="USA"},

       {value="MO",text="Missouri", countryCode="USA"},

       {value="MP",text="Northern Mariana Islands", countryCode="USA"},

       {value="MS",text="Mississippi", countryCode="USA"},

       {value="MT",text="Montana", countryCode="USA"},

       {value="NC",text="North Carolina", countryCode="USA"},

       {value="ND",text="North Dakota ", countryCode="USA"},

       {value="NE",text="Nebraska", countryCode="USA"},

       {value="NH",text="New Hampshire", countryCode="USA"},

       {value="NJ",text="New Jersey", countryCode="USA"},

       {value="NM",text="New Mexico", countryCode="USA"},

       {value="NV",text="Nevada", countryCode="USA"},

       {value="NY",text="New York", countryCode="USA"},

       {value="OH",text="Ohio", countryCode="USA"},

       {value="OK",text="Oklahoma", countryCode="USA"},

       {value="OR",text="Oregon", countryCode="USA"},

       {value="PA",text="Pennsylvania ", countryCode="USA"},

       {value="PR",text="Puerto Rico", countryCode="USA"},

       {value="RI",text="Rhode Island ", countryCode="USA"},

       {value="SC",text="South Carolina", countryCode="USA"},

       {value="SD",text="South Dakota ", countryCode="USA"},

       {value="TN",text="Tennessee", countryCode="USA"},

       {value="TX",text="Texas", countryCode="USA"},

       {value="UM",text="United States Minor Outlying Islands", countryCode="USA"},

       {value="UT",text="Utah", countryCode="USA"},

       {value="VA",text="Virginia", countryCode="USA"},

       {value="VI",text="Virgin Islands", countryCode="USA"},

       {value="VT",text="Vermont", countryCode="USA"},

       {value="WA",text="Washington", countryCode="USA"},

       {value="WI",text="Wisconsin", countryCode="USA"},

       {value="WV",text="West Virginia", countryCode="USA"},

       {value="WY",text="Wyoming", countryCode="USA"},

     }, 

};

 

v  Employee_State was selected and changed to be a Drop Down.

 

v  These properties were then set in the Drop Down eXtension:

 

dataSourceType:

 

     Static Table

 

     tableName:

 

     US_OZ_State

 

onFillDropDown (script):

 

if (ROW.optgroup != null)

{

   ENV.returnValue = null;

}

else

{

   ENV.returnValue = ROW.text;

}

 

 

An example of achieving the same but with the data sourced from an xml file:

 

<?xml version="1.0" encoding="iso-8859-1"?>

 

<states>

   <row>

      <optgroup>US States</optgroup>

   </row>

   <row>

    <code>AK</code>

    <desc >Alaska</desc>

   </row>

   <row>

    <code>NE</code>

    <desc>Nebraska</desc>

   </row>

   <row>

      <optgroup>Australian States</optgroup>

   </row>

   <row>

    <code>ACT</code>

    <desc>Canberra</desc>

   </row>

   <row>

    <code>QLD</code>

    <desc>Queensland</desc>

   </row>

</states>

 

 

Example – Using a drop down as subfile option field

Start Point

A screen like Work With Spool Files:

 

 

Result

The option fields are turned into drop downs to make it simpler to select.

 

Steps

 

1) Add this table definition to your tables_static.txt:

    DefineObjectInstance {

  

      className = "StaticTable",

      name    = "WrkSplF_Options", 

      source  = "inline", 

      rows = {

                {value="",text=""},

                {value="1",text="Snd"},

                {value="2",text="Chg"},

                {value="3",text="Hold"},

                {value="4",text="Del"},

                {value="5",text="Dsp"},

                {value="6",text="Rls"},

                {value="7",text="Msgs"},

                {value="8",text="Attr"},

                {value="9",text="Prt Sts"},

            }, 

 

      };

 

2) Repeat this step for each of the Opt fields ONLY IN THE FIRST PAGE of the screen:

 

v  Make the field a Drop Down and size it appropiately

v  Set the dataSourceType to Static Table and the tableName to WrkSplF_Options

v  Set onSelectValueChanged to

if (ROW.value != "") {FIELD.setValue(ROW.value); SENDKEY("Enter");

3) Save the screen.

 

You can now use the drop down options to work with a file.

Note that in a real application you will need to hide the instructions on the top of the display because they are no longer valid.

 

Example – Dynamic Google Chart 1

By using this extension you will be subject to the Google Terms of Service as outlined in http://code.google.com/apis/chart/tems.html

Start Point

 

You can start with any screen because charts are not associated with fields. For the sake of this example we will start with a blank screen.

 

 

Result

A Pie Google Chart showing the number of employees by department. They use data from static tables that were created by direct scripting (instead of being loaded from the server): 

 

 

 

Steps

 

Add this table definition to your tables_static.txt:


DefineObjectInstance {

       className          = "StaticTable",

       name               = "XHREmployee",

       source             = "sql",

       selectSQLcommand   = "XHRDEPCDE from AXESDEMO.XHREMPTN order by XHRDEPCDE", 

       resultColumnNames  = {"deptCode"},

};

 

 

Add a new element, set its extension type as Google Chart and size it.

Now set these extension properties:

 

tableName: Employees_in_Dept

onLoadChart – intermediate version:

 

/* Load the static tables file */

TABLEMANAGER.loadStaticTables(USERENV.staticTablesFile);

 

/* The department table returned by the selectSQL filled with departments */

var deptTable = TABLEMANAGER.getTable("XHREmployee"); 

 

/* The table to be filled manually based on the department table */

var chartTable = TABLEMANAGER.getTable("Employees_in_Dept"); 

 

/* Get the total number of table rows which is the same as the total number of employee */

var iRows = deptTable.childCount();

 

var savDept = "";

var iEmployeeInDept = 0;

var oChild;

 

/* Traverse the static table, count the number of employees for each department and insert a child row into the table nominated in the tableName property whenever a change of department is detected. Note the order by in the selectSQL statement makes sure the records are ordered by the department code */

 

for (var i = 0; i < iRows; i++)

{

 oChild = deptTable.child(i);

 if (oChild.deptCode != savDept)

 {

   if (savDept != "")

   {

     chartTable.insertChild({chartData:iEmployeeInDept.toString(),chartLabel:savDept});

   }

   savDept = oChild.deptCode;

   iEmployeeInDept = 1;

 }

 else

 {

    iEmployeeInDept++;

 }

}

 

Title: Number+of+Employees|by+Department

 


INTERMEDIATE RESULT 1:

 

 

To add colors you could add the RGB hex color codes to the colors property. In this case because there are 5 departments you would add 5 color codes like this:

 

colors: 00AA00,FFCC00,DDCCFF,ABCDEF,0000AA

 

However, since we don't really know how many departments until the query is executed, the proper way to do it is inside the for loop when the department changes. To do this we need an array of colors to select from, a positional index, a variable to store the entire color string and a variable for the comma separator:

 

/* array of colors to set to the different departments. It should contain enough colors so that no color is repeated.  */

var arrayColors = ["00AA00", "FFCC00" , "DDCCFF", "ABCDEF", "0000AA", "84871C", "FF9900"];

var iColorIndex = 0;

var chco = "";

var colorSep = "";

 

Now we modify the logic inside the for loop to start building the color string:

 

/* Load the static tables file */

TABLEMANAGER.loadStaticTables(USERENV.staticTablesFile);

 

/* The department table returned by the selectSQL filled with departments */

var deptTable = TABLEMANAGER.getTable("XHREmployee"); 

 

/* The table to be filled manually based on the department table */

var chartTable = TABLEMANAGER.getTable("Employees_in_Dept"); 

 

/* Get the total number of table rows which is the same as the total number of employee */

var iRows = deptTable.childCount();

 

var savDept = "";

var iEmployeeInDept = 0;

var oChild;

 

/* Traverse the static table, count the number of employees for each department and insert a child row into the table nominated in the tableName property whenever a change of department is detected. Note the order by in the selectSQL statement makes sure the records are ordered by the department code */

 

for (var i = 0; i < iRows; i++)

{

 oChild = deptTable.child(i);

 if (oChild.deptCode != savDept)

 {

   if (savDept != "")

   {

     chartTable.insertChild({chartData:iEmployeeInDept.toString(),chartLabel:savDept});

 

     /* Make sure we are not passed the last color and if we have start from the first one */

     if (iColorIndex >= arrayColors.length)

     {

       iColorIndex = 0;

     }

     chco += colorSep + arrayColors[iColorIndex];

     iColorIndex++;

     /* Change the color separator so from now one it puts a comma before the next color */

     colorSep = ",";

   }

   savDept = oChild.deptCode;

   iEmployeeInDept = 1;

 }

 else

 {

    iEmployeeInDept++;

 }

}

 

Then we must set the colors property:

 

FIELD.setProperty("colors", chco);

 

The entire script should now look something like this:

 

/* Load the static tables file */

TABLEMANAGER.loadStaticTables(USERENV.staticTablesFile);

 

/* The department table returned by the selectSQL filled with departments */

var deptTable = TABLEMANAGER.getTable("XHREmployee"); 

 

/* The table to be filled manually based on the department table */

var chartTable = TABLEMANAGER.getTable("Employees_in_Dept"); 

 

/* Get the total number of table rows which is the same as the total number of employee */

var iRows = deptTable.childCount();

 

var savDept = "";

var iEmployeeInDept = 0;

var oChild;

 

/* array of colors to set to the different departments. It should contain enough colors so that no color is repeated.  */

var arrayColors = ["00AA00", "FFCC00" , "DDCCFF", "ABCDEF", "0000AA", "84871C", "FF9900"];

var iColorIndex = 0;

var chco = "";

var colorSep = "";

 

/* Traverse the static table, count the number of employees for each department and insert a child row into the table nominated in the tableName property whenever a change of department is detected. Note the order by in the selectSQL statement makes sure the records are ordered by the department code */

 

for (var i = 0; i < iRows; i++)

{

  oChild = deptTable.child(i);

  if (oChild.deptCode != savDept)

  {

    if (savDept != "")

    {

      chartTable.insertChild({chartData:iEmployeeInDept.toString(),chartLabel:savDept});

      /* Make sure we are not passed the last color and if we have start from the first one */

      if (iColorIndex >= arrayColors.length)

      {

        iColorIndex = 0;

      }

      chco += colorSep + arrayColors[iColorIndex];

      iColorIndex++;

      /* Change the color separator so from now one it puts a comma before the next color */

      colorSep = ",";

    }

    savDept = oChild.deptCode;

    iEmployeeInDept = 1;

   }

   else

   {

     iEmployeeInDept++;

   }

}

 

FIELD.setProperty("colors", chco);

 


INTERMEDIATE RESULT 2:

 

 

 

 

The next modification is to make the chart show chart legends to show the number of employees in each department. The logic is very similar to the one used for the colors …

 

var chdl = "";

var chdlSep = "";

 

… store the number of employees in the for loop …

 

for (var i = 0; i < iRows; i++)

{

 oChild = deptTable.child(i);

 if (oChild.deptCode != savDept)

 {

   if (savDept != "")

   {

     chartTable.insertChild({chartData:iEmployeeInDept.toString(),chartLabel:savDept});

 

     /* Make sure we are not passed the last color and if we have start from the first one */

     if (iColorIndex >= arrayColors.length)

     {

       iColorIndex = 0;

     }

     chco += colorSep + arrayColors[iColorIndex];

     iColorIndex++;

     /* Change the color separator so from now one it puts a comma before the next color */

     colorSep = ",";

     /* Legends string with the number of employees. The separator here is the pipe char */

     chdl += chdlSep + iEmployeeInDept.toString(); 

     chdlSep = "|";

 

   }

   savDept = oChild.deptCode;

   iEmployeeInDept = 1;

 }

 else

 {

    iEmployeeInDept++;

 }

}

… and set the property

 

FIELD.setProperty("legends", chdl);

 


INTERMEDIATE RESULT 3:

 

 

 

 

Note that Google Chart doesn't scale the data by default. Hence any value greater than 100 appears as 100. In the above chart there is no visual clue that for example the MANU department has more than four times the number of employees than IS (400 vs. 92). The final step then is to make the chart reflect the real proportions. First add a variable:

 

var percent;

 

Then make the calculation and modify the insertChild() call to use the calculated value:

 

   percent = (iEmployeeInDept * 100) / iRows;

   chartTable.insertChild({chartData:percent.toString(),chartLabel:savDept});

 

The final version of the script should look something like this:

 

/* Load the static tables file */

TABLEMANAGER.loadStaticTables(USERENV.staticTablesFile);

 

/* The department table returned by the selectSQL filled with departments */

var deptTable = TABLEMANAGER.getTable("XHREmployee"); 

 

/* The table to be filled manually based on the department table */

var chartTable = TABLEMANAGER.getTable("Employees_in_Dept"); 

 

/* Get the total number of table rows which is the same as the total number of employee */

var iRows = deptTable.childCount();

 

var savDept = "";

var iEmployeeInDept = 0;

var oChild;

 

/* array of colors to set to the different departments. It should contain enough colors so that no color is repeated.  */

var arrayColors = ["00AA00", "FFCC00" , "DDCCFF", "ABCDEF", "0000AA", "84871C", "FF9900"];

var iColorIndex = 0;

var chco = "";

var colorSep = "";

var chdl = "";

var chdlSep = "";

var percent;

/* Traverse the static table, count the number of employees for each department and insert a child row into the table nominated in the tableName property whenever a change of department is detected. Note the order by in the selectSQL statement makes sure the records are ordered by the department code */

 

for (var i = 0; i < iRows; i++)

{

 oChild = deptTable.child(i);

 if (oChild.deptCode != savDept)

 {

   if (savDept != "")

   {

     percent = (iEmployeeInDept * 100) / iRows;

     chartTable.insertChild({chartData:percent.toString(),chartLabel:savDept});

     /* Make sure we are not passed the last color and if we have start from the first one */

     if (iColorIndex >= arrayColors.length)

     {

       iColorIndex = 0;

     }

     chco += colorSep + arrayColors[iColorIndex];

     iColorIndex++;

     /* Change the color separator so from now one it puts a comma before the next color */

     colorSep = ",";

     chdl += chdlSep + iEmployeeInDept.toString(); 

     chdlSep = "|";

   }

   savDept = oChild.deptCode;

   iEmployeeInDept = 1;

 }

 else

 {

    iEmployeeInDept++;

 }

}

 

FIELD.setProperty("colors", chco);

FIELD.setProperty("legends", chdl);

 


FINAL RESULT:

 



To visualize the PIE chart as a BAR one, change the type property from p to bvs:

 

The chart should now look something like this:

 

The colors are all the same because for this type of chart, the character used to separate the colors inside the color string (chco=) is a pipe char instead of a comma.

 

In the onLoadChart script modify this line

 

colorSep = ",";

 

To this line

 

colorSep = "|";

 

Observations

To make charting more flexible and reusable it is strongly recommended to create a function in USERENV.js so that you are able to parameterize variables according to the chart type. Using the above as an example, you could pass this function a color separator ("," or "|") so that you don't have to change your script.

 

Example – Dynamic Google Chart 2

By using this extension you will be subject to the Google Terms of Service as outlined in http://code.google.com/apis/chart/tems.html

Start Point

 

Please – do the Dynamic Google Chart 1 tutorial before this one. This tutorial assumes that you have done it.

 

In this tutorial we will generate the same chart by generating the entire URL parameter string.  Start with a blank screen:

 

 

Result

A Pie Google Chart showing the number of employees by department. They use data from static tables that were created by direct scripting (instead of being loaded from the server): 

 

 

Steps



v  Add a new element, set its extension type as Google Chart and size it.

Now set these extension properties:

sourceParmString: User String
tableName: Employees_in_Dept
onLoadChart:

/* Load the static tables file */

TABLEMANAGER.loadStaticTables(USERENV.staticTablesFile);

 

/* The department table returned by the selectSQL filled with departments */

var deptTable = TABLEMANAGER.getTable("XHREmployee"); 

 

/* Get the total number of table rows which is the same as the total number of employee */

var iRows = deptTable.childCount();

var oChild;

var savDept = "";

var iEmployeeInDept = 0;

 

/* array of colors to set to the different departments. It should contain enough colors so that no color is repeated.  */

var arrayColors = ["00AA00", "FFCC00" , "DDCCFF", "ABCDEF", "0000AA", "84871C", "FF9900"];

var iColorIndex = 0;

var chco = "chco=";

var colorSep = "";

/* Variable to store the legends */

var chdl = "chdl=";

var chdlSep = "";

/* Variable to store the labels */

var chl = "chl="; 

var chlSep = "";

/* Variable to store the data */

var chd = "chd=t:";

var chdSep = "";

var sURLParms = "chs=" + FIELD.getSize().width + "x" + FIELD.getSize().height+ "&cht=p&chtt=Number+of+Employees|by+Department";

 

var percent;

/* Traverse the static table, count the number of employees for each department and insert a child row into the table nominated in the tableName property whenever a change of department is detected. Note the order by in the selectSQL statement makes sure the records are ordered by the department code */

 

for (var i = 0; i < iRows; i++)

{

 oChild = deptTable.child(i);

 if (oChild.deptCode != savDept)

 {

   if (savDept != "")

   {

     percent = (iEmployeeInDept * 100) / iRows;

     chd += chdSep + percent.toString();

     chdSep = ",";

     chl += chlSep + savDept;

     chlSep = "|";

     /* Make sure we are not passed the last color and if we have start from the first one */

     if (iColorIndex >= arrayColors.length)

     {

       iColorIndex = 0;

     }

     chco += colorSep + arrayColors[iColorIndex];

     iColorIndex++;

     /* Change the color separator so from now one it puts a comma before the next color */

     colorSep = ",";

     chdl += chdlSep + iEmployeeInDept.toString(); 

     chdlSep = "|";

   }

   savDept = oChild.deptCode;

   iEmployeeInDept = 1;

 }

 else

 {

    iEmployeeInDept++;

 }

}

 

sURLParms += "&" + chd + "&" + chl + "&" + chco + "&" + chdl;

FIELD.setProperty("userString", sURLParms);

 

FINAL RESULT:

 



 

Example – Creating Static Tables by Scripting   

Start Point

The System i Main Menu, identified as screen MAIN, with the entry field named CommandLine:       

 

 

Result

A dropdown and a push button are added to the MAIN screen. They use data from static tables that were created by direct scripting (instead of being loaded from the server): 

 

Steps

 

v  The application properties are viewed

 

 

and then edited

 

 

v  These properties were then set in the application's Basic properties:

 

onApplicationStart

 

var t1 = TABLEMANAGER.getTable("TestTable1"); 

 

t1.insertChild({text:"Copy File",value:"CPYF"});

t1.insertChild({text:"Call Program",value:"CALL"});

t1.insertChild({text:"This Job",value:"WRKJOB"});

t1.insertChild({text:"System Status",value:"WRKSYSSTS"});

 

var t2 = TABLEMANAGER.getTable("TestTable2");

 

t2.insertChild({code:"SAL",desc:"Sales"});

t2.insertChild({code:"MKT",desc:"Sales"});

t2.insertChild({code:"ADM",desc:"Administration"});

 

 

v  The application properties were then saved.

 

v  A Push Button is added to the MAIN screen as a new element.

These properties were set:

 

caption

 

Iterate Table 2

 

onClick

 

var t2 = TABLEMANAGER.getTable("TestTable2");

var iLimit = t2.childCount();

var sMessage = "";

for (var i = 0; i < iLimit; i++)

{

   var row = t2.child(i);

   sMessage += row.code + " - " + row.desc + "\r"; 

}

window.alert(sMessage);

 

v  A Drop Down is added to the MAIN screen as a new element.

These properties were set:

 

dataSourceType

 

Static Table

 

tableName

 

TestTable1

 

onSelectedValueChanged

 

var field = FIELDS("CommandLine");

field.setValue(ROW.value);

 

Observations

The drop down is filled from a static table named TestTable1. When an entry is selected it sets the CommandLine field on the MAIN screen to the selected table row's value property/column.

 

The push button reads all the entries (children) in the static table named TestTable2. It formats a string from each rows code and desc values - presenting the result like this:

 

 

The crux of this example is the application start up logic that creates the static tables TestTable1 and TestTable2 by executing script. The annotated logic is:

 

Get a reference to a table named TestTable1, creating it if it does not exist:

 

var t1 = TABLEMANAGER.getTable("TestTable1"); 

 

Create four rows (children) in the table where each child has properties/columns named text and value :

 

t1.insertChild({text:"Copy File",value:"CPYF"});

t1.insertChild({text:"Call Program",value:"CALL"});

t1.insertChild({text:"This Job",value:"WRKJOB"});

t1.insertChild({text:"System Status",value:"WRKSYSSTS"});

 

Get a reference to a table named TestTable2, creating it if it does not exist:

 

var t2 = TABLEMANAGER.getTable("TestTable2");

 

Create three rows (children) in the table - each child has properties/columns named code and desc :

 

t2.insertChild({code:"SAL",desc:"Sales"});

t2.insertChild({code:"MKT",desc:"Sales"});

t2.insertChild({code:"ADM",desc:"Administration"});

 

 

Example – Working with 5250 Cursor

Since many 5250 applications are heavily depending on the cursor for its navigation, a mechanism to control the position the cursor in the back-end 5250 program is required.

 

Supposing that you have an enrol employee screen, and the department field has a prompt function that displays the list of available departments.

 

 

When the prompt function is executed on the Department Code field, a screen showing the department list will appear:

 

 

To select a department in a 5250 terminal, the user will have to position the cursor on the correct line to select a department. However this will not work for a web-based application, so we need to programmatically instruct the program on the back-end to move the cursor to a specific location when the user clicks on a department in order to let the program know which department is selected.

 

The steps below show one way to do this.

 

Apply a hyperlink eXtension to this column

 

Switch the caption property of the hyperlink to scripting mode by clicking on the pencil icon.

Type the following script into the script editor:

 

FIELD.getValue()

 

This instructs the hyperlink to display the original 5250 text as the link’s text.

 

Then type in the following script in the onClick event of the hyperlink:

 

FIELD.set5250Cursor();

SENDKEY("Enter");

 

FIELD.set5250Cursor method will move the logical cursor to the element that is clicked. It then simulates an ENTER keystroke.

 

Example – Multi-lingual Text    

Start Point

The System i Main Menu, identified as screen MAIN, with the entry field named CommandLine:       

 

 

Result

A push button is added to the MAIN screen. When axes is run in the default language or with &lang=en added to the url, the button caption and the alert issued by clicking the button use english captions. 

 

 

 

 

When &lang=xx is added to the url, the xx language caption is shown for the button and in the alert:

 

Steps

 

v  Edit the screen properties and add a new element

 

 

v  Set the element's visualization to push button

 

 

 

v  Set its properties as shown:

 

 

(note that the caption property is a script, not a value)

 

 

v  Edit the file in axes/ts/lang called Custs_Text_en.txt

 

Add the following entries just before the last line:

 

"Button1.Text" : "English Button",

"Button1.Message" : "English message from button 1",

 

v  Create a new file in axes/ts/lang called Custs_Text_xx.txt (Copy it from  Custs_Text_en.txt)

 

Edit it and modify the entries you added in the previous step to be:

 

"Button1.Text" : "xx language Button",

"Button1.Message" : "xx language message from button 1",

 

 

v  From a command line on the iSeries, check  the authority to the file, using wrklnk 'axes/ts/lang/*', and then option 9 against the entry for Custs_Text_xx.txt.

 

Ensure that the authority for *PUBLIC is *R (and only *R). 

 

 

v  Ensure all your changes are saved, and restart axes in run mode, and sign on.

 

On the main screen, the button should show the english caption: English Button

 

And when the button is clicked, the alert should show:  "English message from button 1"

 

 

v  Restart axes in run mode, but add &lang=xx to the url

 

On the main screen, the button should show the xx language caption: xx language Button

 

And when the button is clicked, the alert should show:  "xx language message from button 1"

 

Example – Visibility control (over a screen element - dynamic)    

Start Point

The System i Main Menu, identified as screen MAIN

 

 

Result

A push button is added to the MAIN screen. When the button is clicked it makes the screen title element ("System i Main Menu") disappear.

 

 

 

Steps

v  Start axes in developer mode, and go to the first screen.

 

v  Click on the "System i Main Menu" element on the screen. (The screen title element)

 

 

v  Name the element "screenTitle". (Now that the element is named, it can be referenced by other elements on the screen) Save your changes.

 

 

 

v  Click on the Edit Screen button and Add a new element

 

 

v  Set the new element's visualization to push button

 

 

 

v  Set the push button's caption property to:

 

Make title invisible

 

v  Set the onClick property to:

 

 

var TitleField = FIELDS("screenTitle");

TitleField.setProperty("visible", false);

TitleField.refresh();

 

 

 

 

 

v  Save your changes. You should now be able to click the button and make the screen title disappear

 

Observations

 

In the onClick code:

 

var TitleField = FIELDS("screenTitle");

 

is allowing the button element to work with the screen title element, by accessing the FIELDS collection, using the name "screenTitle" as the key.

 

TitleField.setProperty("visible", false);

 

All elements have a visible property, so set the screen title's visible property to false, using this method.

 

TitleField.refresh();

 

When you change a property of an element that changes its appearance, you usually need to tell the element to re-draw itself. This is done with the .refresh() method.

 

 

Example – Using a date format not available in the Date eXtension   

Start Point

The Maintain Employee Information subfile identified as screen XHRRPGTRN_Select.

 

 

Result

Date of Birth displaying in format YY/MM/DD not available in the list of extension formats:

 

Steps

 

v  Edit the language defaults file /ts/lang/Texts_Cust_xx.txt where xx is the language code.

 

v  Find the dateFormatDisplay setting. Change the shipped default from:

"dateFormatDisplay" : "d/mm/yy",

to

"dateFormatDisplay" : "y/mm/dd",

 

v  Save and close the file.

 

v  Clear your browser cache to pick up the new file version.

 

v  Start axes in developer mode go to the Maintain Employee screen, and edit the screen.

 

v  Select one of the date fields in the subfile and change it to use the Date eXtension.

 

 

v  Save the screen. YY/MM/DD is now your language default dateFormatDisplay.

 

v  Likewise, if most of your programs use YY/MM/DD to store dates but DD/MM/YY to display them, leave the dateFormatDisplay with its shipped default value and change the dateFormatServer value.

 

Example – Visibility control over a new element - dynamic    

Start Point

The System i Main Menu, identified as screen MAIN

 

 

Result

Two push buttons are added to the MAIN screen. When the second button is clicked it makes the first button disappear.

 

 

 

 

Steps

v  Start axes in developer mode, and go to the first screen, and edit the screen.

 

 

v  Add a new element and make it a pushbutton

 

Make its caption property "Button1"

 

Make its name property "Button1" (this allows other elements on the screen to set its properties)

 

 

v  Add another element, and make it a pushbutton.

 

 

Make its caption property "Make Button1 invisible"

 

Make its onClick property:

var Button1 = FIELDS("Button1");

Button1.setProperty("visible", false);

Button1.refresh();

 

 

 

 

 

v  Save your changes. You should now be able to click the second button and make the first button disappear

 

 

 

Example – Dynamic Tables using a condition that evaluates a numeric value

 

Choose any screen and add:

 

1. A New Element type Label. Set its caption to "Salaries greater than"

 

2. A New Element. Leave it with the Default Visualization. Set its name to Request_Salary

 

3. A New Element type DropDown. Set its name to Employees

 

Set its dataSourceType to Dynamic.

Set its onFillDropDown property to: ROW.lastName + " (" + ROW.salary + ")";

Set its SQL Query Name to EmployeeSalaries

Set its SQL Variables property to:

 

var salaryValue = parseFloat(FIELDS("Request_Salary").getValue());

 

if ( (isNaN(salaryValue)) || ( typeof(salaryValue) != "number") )

{

  ENV.SQL.SQLVariableSalary = 0;

}

else

{

  ENV.SQL.SQLVariableSalary = salaryValue;

}

 

4. A New Element type Push Button:

 

Set its caption to Select Employees

Set its onClick property to FIELDS("Employees").refresh();

 

The screen with the New Elements should look something like this:

 

 

 

5. Edit your Dynamic Tables file and add this table:

 

    DefineObjectInstance {

      className         = "DynamicTable",

      name              = "EmployeeSalaries",  

      source            = "sql",

      selectSQLcommand  = "XHRSURNME,XHRGIVNME, XHRSALARY from AXESDEMO.XHREMPTN where XHRSALARY > ':SQLVariableSalary' ",

      resultColumnNames  = { "lastName", "firstName", "salary"},

    };