In order to provide additional, and advanced, functionality to your O4W Form, you may choose to create and modify a "commuter module" that the form will invoke.
If you wish to use a commuter module, during the form definition process (on the "Behavior" tab) you _must_ check the "Call commuter module for events on this form?" radio button. You can then select which events you wish to be sent to the commuter module; choose which "form-level" events you would like to process from the "Events to call commuter module" section of the "Behavior" tab, and choose which "field-level" events you would like to process from the "Value Input/Display" popup of the "Edit/Display" tab. The form level events include "Pre-Read", "Post-Read", "Pre-Write", "Post-Write", "Pre-Delete", "Post-Delete", "Select", and "Tab". The field level events include "Entering field" and "Leaving field".
[Note that, while the temptation to add in EVERY event for EVERY control is great, this should be avoided if at all possible. While the underlying web technology behind O4W allows for much greater interactivity than the "web 1.0" straight-html forms of a few years ago, the web browser is NOT yet a replacement for the desktop, and attempting to have an O4W Form behave exactly the same as an OpenInsight (desktop-based) form - in terms of handling every event - will probably leave developers and end users alike frustrated. If for no other reason than Internet delays and slowdowns, interacting with the commuter module (which requires a "round trip" from the web browser to the web server, and additional processing time inside of OpenInsight, O4W, and the commuter module itself) can take a noticable time. For more performant forms (and therefore happier users), it is important to only 'capture' those events that need to be handled.]
At the end of the form definition process, you will be prompted to name the commuter module; you will also be prompted to have O4W automatically create and compile the form "skeleton". If this is the first time through the form definition process for this form with the "Call commuter module for events on this form?" button selected, this will be your only option; if, however, the commuter module may have been created (and manually modified) previously, you are also presented the option of downloading (into Windows notepad) the updated auto-generated commuter module code. You can then manually copy and paste any relevant changes from Windows notepad into your OpenInsight stored procedure.
The automatically generated 'skeleton' of the commuter module will include code for each of the events you have specified (PRE-READ, PRE-WRITE, etc.) in the form definition process. You should then add additional code to perform the desired actions during those events.
The O4W Form, when run, may raise several different "events"; if you have created a commuter module, and choose to have those events passed to your commuter module, you may programmatically alter how O4W behaves.
When the event is raised, and the commuter module is called, it will be passed the name of the control (if applicable) that is generating the event, and the type of event. There are also several "named common" elements that you may access in your commuter module for additional flexibility. These elements include formDef@ (the definition of this O4W form), @ID (if applicable), @RECORD (if applicable), FormName@ (the name of this O4W Form), bIsNew@ (set to "1" during READ processing if this is a new record), and UserFields@ (set to the value(s) of any user defined field(s) during WRITE processing, and also set during processing of the "POPUP" event).
The commuter module 'skeleton' will also contain code to extract the name of the field that is generating the event (if applicable), and its current value (again, if applicable).
When returning from the commuter module to the O4W Form, the variable "rtnValue" must be set properly to allow, or prevent, the standard form operation for that event from occurring. Set the rtnValue to "1" to allow the event to continue processing (the default behavior), or "0" to halt that event (note that in certain circumstances described below, rtnValue may also be set to "-1" for special operations). There are also additional "named common" elements that may be set to effect the O4W Form. These elements include statMsg@ (set to have the O4W Form display a message), redirectTo@ (set after WRITE or DELETE processing to redirect the browser page), and UserFields@ (set to the value(s) of any user defined field(s) during READ processing, and also set during processing of the "POPUP" event).
The events that are raised, and that may be handled through the commuter module, include:
Form level events:
- PRE_READ
- POST_READ
- READ_CHECK
- PRE_WRITE
- POST_WRITE
- PRE_DELETE
- POST_DELETE
- SELECT
- TAB
Field level events:
- PRE_FIELD
- POST_FIELD
- CLICK
- POPUP (O4W 1.1+)
The PRE_READ Event
The PRE_READ event is raised before the O4W Form reads a data record. It is called during two different processes - if there is a 'picklist', or list of possibly matching records returned as the result of a search, the PRE_READ event is raised for EACH record that is displayed in the list of records. In addition, it is called to read the actual data record that is to be displayed or edited in the main body of the form. If the "Add New" button has been pressed, then bIsNew@ is set to "1".
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record. The code may also set the UserFields@ variable to define 'default values' for any user-defined fields.
To continue normal processing, set rtnValue to "1". To abort the read event, set rtnValue to "0" (and optionally set statMsg@ to indicate the reason for the abort). If you wish to perform the same function as the READ event in the commuter module, set the @RECORD variable as desired, and then set rtnValue to "-1" (this instructs the O4W Form that you wish to skip the actual READ, but wish to continue normal processing beyond that).
If for example you want to read records for a specific user your code in the PRE_READ event could be as follows:
USERNO = "X_UNKOWN_X" @RECORD = "" OPEN 'WEB_USERS' TO USER.FL Else statMsg@ = "Unable to access WEB_USERS table" rtnValue=0 Return rtnValue End OPEN 'REVCONT' TO REVCONT.FL Else statMsg@ = "Unable to access REVCONT table" rtnValue=0 Return rtnValue End TEMPID = O4WCookie("USERID") READ TEMP.INFO FROM O4WTEMPFILE%, TEMPID Then READ USER.ITEM FROM USER.FL, TEMP.INFO<1> Then READ MOEREC FROM REVCONT.FL,USER.ITEM<5> THEN USERNO=MOEREC<1> sno_user = Xlate("SERIALNO_HISTORY", CTLENTID, 2, "X") If USERNO # SNO_USER Then statMsg@="Serial number requested is not for your user ID, please call Revelation Software at (800) 262-4747." rtnValue=0 Return rtnValue End End End End
The POST_READ Event
The POST_READ event is raised after the O4W Form reads a data record. It is called after the actual data record that is to be displayed or edited in the main body of the form has been read.
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record, and @RECORD for the contents of the data record. The code may also set the UserFields@ variable to define 'default values' for any user-defined fields.
The READ_CHECK Event
The READ_CHECK event is raised before a DELETE or WRITE of the record. It is done as part of the "optimistic locking" algorithm, which seeks to verify that the record has not been changed by another user during web-based processing. Before writing or deleting the record, the original record is re-read, and - if it has not been changed - the record is locked and then updated or deleted. The READ_CHECK event is raised so that the commuter module can recreate any operations performed during the original PRE_READ event (note that this event is automatically added if the PRE_READ event has been selected).
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record.
To continue normal processing, set rtnValue to "1". If you wish to perform the same function as the READ event in the commuter module, set the @RECORD variable as desired, and then set rtnValue to "-1" (this instructs the O4W Form that you wish to skip the actual READ, but wish to continue normal processing beyond that). Note there is no option to "abort" the READ_CHECK operation by setting rtnValue to "0" - if you wish to abort WRITE or DELETE processing, you must handle that in the PRE_WRITE or PRE_DELETE event, as appropriate.
The PRE_WRITE and PRE_DELETE Events
The PRE_WRITE and PRE_DELETE events are raised before the O4W Form writes or deletes the data record, respectively.
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record. The @RECORD variable will contain the updated values that are to be written or deleted. If defined, any user-defined field values will be set in the UserFields@ variable.
To continue normal processing, set rtnValue to "1". To abort the write or delete event, set rtnValue to "0" or "-1"(and optionally set statMsg@ to indicate the reason for the abort). If set to "-1", the O4W Form will return to the main entry/selection page; if set to "0", the current page remains unchanged.
The POST_WRITE and POST_DELETE Events
The POST_WRITE and POST_DELETE events are raised after the O4W Form successfully writes or deletes the data record, respectively.
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record. The @RECORD variable will contain the updated values that have been written or deleted. If defined, any user-defined field values will be set in the UserFields@ variable.
Optionally set statMsg@ to any message you wish to display. Optionally set redirectTo@ to the URL of any page you wish to transfer to; if not specified, the O4W Form will return to the main entry/selection page. Note that the value of the rtnValue variable is not applicable here.
The SELECT Event
The SELECT event is raised when generating the list of keys to choose from in a "picklist" type O4W Form, or when generating the search results in a "search" O4W Form.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the list record that is to be created. The list record is an entry in the SYSLISTS table that contains a list of all the IDs that the user can select from.
If rtnValue is set to "1", normal processing continues. If rtnValue is set to "0", NO records are selected. If rtnValue is set to "-1", the O4W Form will proceed with whatever keys have been placed into the list record without using its own algorithm to select the keys. In this way, the commuter module can use its own logic, overriding the built-in O4W Form search/selection logic.
For example if you wanted to change the sort order of your “picklist” you can insert the following code on the SELECT event:
cmd = "SELECT ROYSCHED BY TITLE_ID BY LORANGE" Call Rlist(cmd, 4, CtlEntId) rtnValue = -1
The TAB Event
The TAB event is raised when the user clicks on any tab of the O4W Form, or uses the optional Back or Next buttons to navigate through the tabs.
The commuter module code may examine the passed "CtlEntId" parameter for the number of the current tab, and the UserFields@ variable for the number of the tab the user wishes to move to. Note that the commuter module 'skeleton' code will translate the current tab number into a tab name, available in the variable "tabname".
If rtnValue is set to "1", normal processing continues. If rtnValue is set to "0", the request to switch tabs is aborted; any message stored in statMsg@ will be displayed.
The PRE_FIELD Event
The PRE_FIELD event is raised when the specified field is about to get the "focus" on the web page.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page input control that is about to get the focus. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W form definition, available in the variable "fieldName".
If statMsg@ is set, its contents are displayed. Note that the value of the rtnValue variable is not applicable here.
The POST_FIELD Event
The POST_FIELD event is raised when the specified field is about to lose the "focus" on the web page.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page input control that is about to lose the focus. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W form definition, available in the variable "fieldName". In addition, the current value of the field is available in the variable "currValue".
If statMsg@ is set, its contents are displayed. If rtnValue is set to "0", focus is re-set back to this field (so the user is unable to exit this input control). Note that MANY users find this action extremely annoying, and it should thus be used only when absolutely required.
Never set the statMsg@ and rtnValue = -1. This generates an infinite loop (since the post_field is triggered by the ‘lost focus’ event, and you get into a loop where the form loses focus, you go into the commuter module, you set rtnValue = -1 (which sets us back to that same field), then you set statMsg@ (which puts up a message box)…which maes us lose focus on the field – and son on).
The CLICK Event
The CLICK event is raised when either the specified user-defined button is clicked, or the specified "dummy" popup button is clicked.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page button that has been pressed. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W form definition, available in the variable "fieldName". The variable "clickType" will also be set, either to "CLICK" (for a user-defined button) or "POPUP" (for a "dummy" popup button).
Note that the values of the rtnValue and statMsg@ variables are not applicable here.
The POPUP Event
The POPUP event (available in O4W 1.1 and above) is raised when specified "popup" field has returned a value.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page button that is associated with this popup. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W form definition, available in the variable "fieldName". The userFields@ variable will contain the return value from the popup.
The commuter module can change what value is placed into the associated field by changing the value of the userFields@ variable. The field where the popup value is to be placed can also be changed by changing the value of the "CtlEntId" variable.
If statMsg@ is not null, any text specified in statMsg@ is displayed. If rtnValue is set to "1", normal processing continues. If rtnValue is set to "0", the returned value from the popup is NOT placed in the associated field.
Once the commuter module has been invoked for a particular event, it most likely will need to access, and potentially update, elements of the browser page.
When the O4W Form was defined, fields from the specific table's dictionary, and any user-defined fields, were selected for use as "input" controls (textboxes, password boxes, etc.). When the form is actually created for the web browser, each input control is given a unique name. These names are NOT based on the "real" OpenInsight field name; rather they are based on the _location_ of the given field in the O4W Form definition.
For example, if there are 5 fields (FIRST_NAME, LAST_NAME, CITY, STATE, and ZIP) that are defined in an O4W Form named "SAMPLE", there may be 5 textboxes on the web form that is generated from SAMPLE. These 5 textboxes are named FIELD_1, FIELD_2, FIELD_3, FIELD_4, and FIELD_5. The contents of FIELD_1 are associated with the FIRST_NAME field; the contents of FIELD_2, with the LAST_NAME field; and so on.
The commuter module utility function O4WCommuterUtility has been developed to provide the "conversions" from field names to web form elements. To find which field name is associated with the current input control, we might have code like the following:
MyControlName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$)
To find what input control name is used for a particular field name, we need to determine the location of that field name in the list of all field names used by the form. To find the input control name for the field named "STATE", for example, we could use the following code:
stateElement = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "STATE")
An additional complication is multivalue fields, and associated multivalue sets that may increase or decrease in number. The O4WCommuterUtility will use the information passed in with the current control (via the parameter CtlEntId) to determine the proper value to extract from the current field:
currValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$)
If you wish to extract information from another field, simply specify that field name:
otherValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$, "STATE")
If the additional field (in this case, "STATE") is part of a multivalue association with the current field, or neither the current field nor the additional field are multivalued, you should pass in the CtlEntId variable as the first parameter; however, if the current field is multivalued and the additional field is not multivalued, or the two fields are part of different associations, you should pass in a null string ("") for the first parameter to prevent the incorrect value from being retrieved.
You can also use O4WCommuterUtility to return the names of other web page elements that you might wish to manipulate. The O4W Form is laid out as a set of 'grids', or tables of elements; each element of the table is called a "cell", and can have specific properties defined (like background color, alignment, etc.). Each field on the O4W Form consists of a label and one (or more, for multivalued data) "input controls", each in their own cells; within the cells, the contents are defined within a "section" that contains the actual text or control. Depending on what you wish to change, you can access:
- The "cell" of the grid that contains the label, with the O4WUTILITY_FORMELEMENT_LABEL_CELL$ parameter;
- The "cell" of the grid that contains the input control, with the O4WUTILITY_FORMELEMENT_CONTROL_CELL$ parameter;
- The "section" within the label's cell where the actual label text is defined, with the O4WUTILITY_FORMELEMENT_LABEL_SECTION$ parameter;
- The "section" within the input control's cell where the actual input control is defined, with the O4WUTILITY_FORMELEMENT_CONTROL_SECTION$ parameter;
- The input control itself, with the O4WUTILITY_FORMELEMENT_CONTROL$ parameter
The following code was generated as the "skeleton" commuter module for an O4W Form named "CUSTOMER". The Pre-read, Post-write, and Tab form-level events have been selected, and there are a number of field-level events (including Pre-field and Post-field on a number of fields, a "dummy" popup button, and a user-defined button).
FUNCTION CUSTOMER_O4W_COMMUTER_MODULE(CtlEntId, Event, Request) * Auto-generated by O4W_DEFINE_FORM at 10:51:51 06 JAN 2010 * Standard equates $Insert O4WEquates $Insert O4WFormEquates $Insert O4W_COMMUTER_COMMON rtnValue = 1 Begin Case Case event _eqc 'PRE_READ' * called before reading record from table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * examine variable bIsNew@ to determine if this is a new record, or a record that is supposed to exist * set variable 'rtnValue' to 0 (rtnValue=0)to disable further event processing * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should: * - fill the @RECORD variable with the desired record contents * - fill the userFields@ variable with the desired values for the user-defined fields * ) * set variable 'statMsg@' to desired error/status text Case event _eqc 'READ_CHECK' * called before write or delete to verify record has not been changed by another user * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should fill the * @RECORD variable with the desired record contents) Case event _eqc 'POST_WRITE' * called after writing record to table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * set variable 'statMsg@' to any desired 'success' message * set variable 'redirectTo@' to url to transfer to after success Case event _eqc 'TAB' * called when 'tab' clicked, or back/next button clicked * variable 'ctlentid' is number of current tab tabname = formDef@<tabnames$, ctlentid> * variable userFields@ is number of 'next' tab * set variable 'rtnValue' to 0 (rtnValue=0) to abort tab change * set variable 'statMsg@' to any desired error/status text * to examine all the fields on the current tab: num.fields = dcount(formDef@<attr$>, @VM) for each.field = 1 to num.fields if formDef@<ValueTab$, each.field> = tabname then fieldName = formDef@<attr$, each.field> thisValue = O4WGetValue('FIELD_':each.field) end next each.field Case Event _eqc 'PRE_FIELD' * called when specified field has gotten 'focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) Begin Case Case fieldName _eqc 'WEBSITE' End Case Case Event _eqc 'POST_FIELD' * called when specified field has 'lost focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) currValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$) Begin Case Case fieldName _eqc 'WEBSITE' Case fieldName _eqc 'ADDRESS1' Case fieldName _eqc 'CITY' End Case Case Event _eqc 'CLICK' * called when user-defined or 'dummy' popup button clicked fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) clickType = O4WCommuterUtility(CtlEntId, O4WUTILITY_CONTROLTYPE$) Begin Case Case fieldName _eqc 'GROUP' * called from clicking 'popup' button Case fieldName _eqc 'SampleBtn' * called from clicking user-defined button End Case End Case Return rtnValue
This is the code that is built by O4W without any modification. At this point, the code can be modified (using both "regular" BASIC+ programming code, and/or O4W APIs).
Example 1: Setting A Field's Value Based On Another Field
Let us suppose, for example, that we have an OpenInsight stored procedure that, when given the name of the city, can tell us the state that city is in. Let us set our STATE field's value based on the value of CITY. When the CITY field has lost "focus", we can populate the STATE field with our calculated result.
Therefore, in the "POST_FIELD" section of the skeleton code, we look for the 'CITY' case. We can add the following code:
Call FindStateFromCity(currValue, ourState) * Determine the web form's "input element" name for the STATE field stField = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "STATE") * and update that element O4WUpdate(stField, ourState, O4WResponseStyle(%%''%%,'1'))
Assuming the FindStateFromCity stored procedure returns (in the second parameter) the name of the state based on the city (in the first parameter), we just take the returned value and put it into the web form where the "STATE" field is.
As described above, we need to translate from the field name ("STATE") to the input control name; to do this, we use O4WCommuterUtility to find STATE's location in the list of fields.
Once we have determined the input control name, we use the O4W API call O4WUpdate to set that control's text to the value in the "ourState" variable. We use the O4WResponseStyle call to indicate that we only wish to update the text (and not the style) of the input control.
Example 2: Generating a POST-WRITE Redirect
Let us suppose that, after writing the record, we wish to display a specific message, and then transfer control to the Revelation home page (rather than back the start of the O4W Form). To achieve this, we must modify the code in the POST_WRITE case as follows:
redirectTo@ = "http:%%//%%www.revelation.com" statMsg@ = "Thank you for updating this record! You will now be redirected to the Revelation Home Page..."
Example 3: Aborting Form Read
As an example, perhaps we wish to forbid reading of records on the same day they were created. In the Pre-read event, we can check if the requested record is 'forbidden', and if it is, abort the read (with an appropriate error message). To achieve this, in the PRE_READ case, we can add the following code:
if bIsNew@ <> "1" then * Not a new record - find out its creation date createDate = xlate("CUSTOMER", @ID, 27, "X") If createDate = DATE() Then rtnValue = 0 statMsg@ = "Invalid record requested; not yet posted" End End
Example 4: Acting On A User-Defined Button
To display a static message (using the O4WError API call) when a user-defined button is clicked, we can add the following code to the "SampleBtn" case:
O4WError("This button intentionally left blank")
Example 5: Updating A ListBox
To update a more complex control, like a listbox, defined as the "input control" for another field, we must first derive the "descriptions" and their associated "code values" that will be placed in the list box. We must then replace the list box control contents entirely; we do that by actually replacing the "section" where that control is located. For example, if we wished to populate a list box in the "ADDRESS2" field when we exit the "ADDRESS1" field, we can add the following code to the "ADDRESS1" case in the "POST_FIELD" section:
* Change the 'address2' list box Call findSubAddress(currValue, DESCS, CODES) add2Control = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "ADDRESS2") add2Section = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL_SECTION$, "ADDRESS2") o4wSectionStart(add2Section, O4WResponseStyle()) O4WListbox(DESCS, CODES, add2Control) O4WSectionEnd(add2Section)
Assuming the findSubAddress stored procedure returns (in the second and third parameters) a list of descriptions (@VM delimited), and their associated code values, for the secondary address based on the primary address (in the first parameter), we can take the returned values and put them into the web form where the "ADDRESS2" field is.
As described above, we need to translate from the field name ("ADDRESS2") to the input control name; to do this, we use O4WCommuterUtility to find ADDRESS2's location in the list of fields. We also use O4WCommuterUtility to retrieve the name of the "section" where ADDRESS2's input control is located.
Once we have determined the input control name, and the section where it is defined, we use the O4W API calls O4WSectionStart, O4WListBox, and O4WSectionEnd to effectively recreate that very small section of the browser's page. The O4WResponseStyle modifier, invoked with no parameters, indicates that we will be replacing the entire element (text, style, attributes, etc.). Once we have recreated the section, we create the new list box, and our new descriptions are now available.
Completed, Modified Commuter Module
FUNCTION CUSTOMER_O4W_COMMUTER_MODULE(CtlEntId, Event, Request) * Auto-generated by O4W_DEFINE_FORM at 10:51:51 06 JAN 2010 * Standard equates $Insert O4WEquates $Insert O4WFormEquates $Insert O4W_COMMUTER_COMMON rtnValue = 1 Begin Case Case event _eqc 'PRE_READ' * called before reading record from table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * examine variable bIsNew@ to determine if this is a new record, or a record that is supposed to exist * set variable 'rtnValue' to 0 (rtnValue=0)to disable further event processing * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should: * - fill the @RECORD variable with the desired record contents * - fill the userFields@ variable with the desired values for the user-defined fields * ) * set variable 'statMsg@' to desired error/status text if bIsNew@ <> "1" then * Not a new record - find out its creation date createDate = xlate("CUSTOMER", @ID, 27, "X") If createDate = DATE() Then rtnValue = 0 statMsg@ = "Invalid record requested; not yet posted" End End Case event _eqc 'READ_CHECK' * called before write or delete to verify record has not been changed by another user * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should fill the * @RECORD variable with the desired record contents) Case event _eqc 'POST_WRITE' * called after writing record to table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * set variable 'statMsg@' to any desired 'success' message * set variable 'redirectTo@' to url to transfer to after success redirectTo@ = "http:%%//%%www.revelation.com" statMsg@ = "Thank you for updating this record! You will now be redirected to the Revelation Home Page..." Case event _eqc 'TAB' * called when 'tab' clicked, or back/next button clicked * variable 'ctlentid' is number of current tab tabname = formDef@<tabnames$, ctlentid> * variable userFields@ is number of 'next' tab * set variable 'rtnValue' to 0 (rtnValue=0) to abort tab change * set variable 'statMsg@' to any desired error/status text * to examine all the fields on the current tab: num.fields = dcount(formDef@<attr$>, @VM) for each.field = 1 to num.fields if formDef@<ValueTab$, each.field> = tabname then fieldName = formDef@<attr$, each.field> thisValue = O4WGetValue('FIELD_':each.field) end next each.field Case Event _eqc 'PRE_FIELD' * called when specified field has gotten 'focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) Begin Case Case fieldName _eqc 'WEBSITE' End Case Case Event _eqc 'POST_FIELD' * called when specified field has 'lost focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) currValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$) Begin Case Case fieldName _eqc 'WEBSITE' Case fieldName _eqc 'ADDRESS1' * Change the 'address2' list box Call findSubAddress(currValue, DESCS, CODES) add2Control = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "ADDRESS2") add2Section = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL_SECTION$, "ADDRESS2") o4wSectionStart(add2Section, O4WResponseStyle()) O4WListbox(DESCS, CODES, add2Control) O4WSectionEnd(add2Section) Case fieldName _eqc 'CITY' Call FindStateFromCity(currValue, ourState) * Determine the web form's "input element" name for the STATE field stField = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "STATE") O4WUpdate(stField, ourState, O4WResponseStyle(%%''%%,'1')) End Case Case Event _eqc 'CLICK' * called when user-defined or 'dummy' popup button clicked fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) clickType = O4WCommuterUtility(CtlEntId, O4WUTILITY_CONTROLTYPE$) Begin Case Case fieldName _eqc 'GROUP' * called from clicking 'popup' button Case fieldName _eqc 'SampleBtn' * called from clicking user-defined button O4WError("This button intentionally left blank") End Case End Case Return rtnValue
Form-level events (in order of occurrence):
CREATE: called when the form is first invoked (via link, bookmark, etc.)
SELECT: called with information needed to select one or more records
PRE_READ: called before reading specific record
POST_DRAW: called after all fields have been drawn
POST_READ: called after all fields have been populated
[Field-level events occur while the user navigates around the page; see below for details]
READ_CHECK: called to validate the underlying record has not changed
PRE_WRITE/PRE_DELETE: called before saving or deleting the record
POST_WRITE/POST_DELETE: called after saving or deleting the record
Field-level events (each may occur multiple times/form):
- TAB: called when the user clicks on a tab
- (in order of occurrence)
PRE_FIELD: called when a field has just received the focus
CHANGE: called when the field’s value has changed
POST_FIELD: called when a field has lost focus
- CLICK: called for a button or popup
- POPUP: called when a popup has returned
Event | Available/Passed-in Parameters | Special Return Information |
---|---|---|
CREATE | CTLENTID=unique form ID | |
SELECT | CTLENTID=list name to create | |
PRE_READ | CTLENTID=@ID bIsNew@ | statMsg@ |
POST_DRAW | bIsNew@ statMsg@ |
|
POST_READ | CTLENTID=@ID @RECORD userFields@ | |
READ_CHECK | CTLENTID=@ID bIsNew@ | statMsg@ |
PRE_WRITE | CTLENTID=@ID @RECORD userFields@ | statMsg@ |
PRE_DELETE | CTLENTID=@ID @RECORD userFields@ | statMsg@ |
POST_WRITE | CTLENTID=@ID @RECORD userFields@ | statMsg@ redirectTo@ |
POST_DELETE | CTLENTID=@ID @RECORD userFields@ | statMsg@ redirectTo@ |
TAB | CTLENTID=current tab # userFields@=new tab # | |
PRE_FIELD | CTLENTID=control causing event FIELDNAME=name of field | |
CHANGE | CTLENTID=control causing event FIELDNAME=name of field CURRVALUE=current field value | |
POST_FIELD | CTLENTID=control causing event FIELDNAME=name of field CURRVALUE=current field value | |
CLICK | CTLENTID=control causing event FIELDNAME=name of field CLICKTYPE=type of field | |
POPUP | CTLENTID=control to receive result FIELDNAME=name of destination field userFields@=popup results | statMsg@ userFields@=popup results |
Label | Action Parameter | Generates Value Of Type: |
---|---|---|
1 | O4WUTILITY_FORMELEMENT_LABEL_CELL$ | id=”LABEL_xx” |
2 | O4WUTILITY_FORMELEMENT_CONTROL_CELL$ | id=”VALUE_xx” |
3 | O4WUTILITY_FORMELEMENT_LABEL_SECTION$ | id=”LABELCELL_xx” |
4 | O4WUTILITY_FORMELEMENT_CONTROL_SECTION$ | id=”VALUECELL_xx” |
5 | O4WUTILITY_FORMELEMENT_CONTROL$ | id=”FIELD_xx” |
6 | O4WUTILITY_FORMELEMENT_TEXT$ | id=”LINK_xx” |
A | O4WUTILITY_FORMELEMENT_CLASS_CONTROL_CELL$ | class=”classCellxx” |
B | O4WUTILITY_FORMELEMENT_CLASS_CONTROL_SECTION$ | class=”valueCellxx” |
C | O4WUTILITY_FORMELEMENT_CLASS_CONTROL$ | class=”classInputxx” |
D | O4WUTILITY_FORMELEMENT_CALSS_TEXT$ | class=”classLinkxx” |
E | (must create manually if needed) | class=”labelCellxx” |
Other O4WCommuterUtility Values:
Action Parameter | Returns |
---|---|
O4WUTILITY_ControlType$ | type of control (FIELD, LINK, etc.) |
O4WUTILITY_FieldName$ | name of field given control |
O4WUTILITY_Value$ | value in field (if available) |
O4WUTILITY_TableName$ | name of table containing given control |
On a multi-tab O4W Form-wizard designed form, you might occasionally wish to programmatically disable one or more tabs (for example, certain tabs might contain information that's not appropriate or relevant for the current record). You can use the commuter module and the "tabs" plugin to achieve this.
Unfortunately, there's no call in the commuterutility to tell us "real time" what all the tabs are, so we have to hard-code which tabs you'll want to disable or enable - so this means that if you go in and add a new tab, you'll need to remember to update your commuter module logic.
So, let's say that you want to programmatically disable the second and fourth tabs. Each tab has a tab number, starting from 0, so the second and fourth tabs are really tab #1 and #3. We can put the following code into the 'post-write' event:
If bDoDisableTabs = "1" then action = "disabled" End Else action = "enabled" End Command = '"option","':action:'",[1,3]'
* The following line invokes the 'tabs' plugin on our O4WDATATABLE section, which is the name the O4W Form Wizard uses for the section that contains the tabs
* It passes in the command line that we've just built, which is something like "option","disabled",[1,3] or "option","enabled",[1,3]
O4WPlugin("O4WDATATABLE", 'tabs', command)
The only complex bit is the line that builds the "command" string that we pass into the plugin. It reads
Singlequote doublequote option doublequote comma doublequote singlequote colon action colon singlequote doublequote comma bracket 1 comma 3 bracket singlequote
If we only need to enable or disable a single tab, then instead of putting the tab numbers inside braces, we can just pass in that single tab number, so if we just wanted to disable or enable the fourth tab (instead of both the second and fourth tabs), the Command code would look like this:
Command = '"option","':action:'",3'
On an O4W Form-wizard designed form, you might occasionally wish to programmatically show or hide one or more "groups" that you've defined in form definition process (for example, certain groups might contain information that's not appropriate or relevant for the current record). You can use the commuter module and O4WQualifyEvent to achieve this.
In order to implement this functionality, you must know the name of at least one field that's defined in the group. Then, using the commuterutility and that field name, we can find the section that contains the group that this field belongs in, and then show or hide the entire section, as appropriate.
* put this in the "post draw" event in the commuter module
* get a "handle" to the group that this element is part of – for example, let's pretend that the field is named REPORTS_TO, and we want to hide the entire group that contains REPORTS_TO
sectionExample = O4WCommuterUtility("", O4WUTILITY_SECTIONNAME$, "REPORTS_TO") if sectionExample <> "" then if bHideSection = "1" then O4WQualifyEvent("", "hide", sectionExample) ;* we use "" in the first parameter to tell the qualify event we want the hide to take effect immediately End else O4WQualifyEvent("", "show", sectionExample) ;* we use "" in the first parameter to tell the qualify event we want the show to take effect immediately End End
Report-level events (in order of occurrence):
CREATE: called when the report is first invoked (via link, bookmark, etc.)
SELECT: called with information needed to select the records to display (NOTE: if this event is marked, but you don’t actually code anything in the commuter module, you MUST remember to set the return value to “0” or all searches will fail)
PRE_SEARCH: called during report page generation to allow for enhancements to the ‘search’ field
PRE_READ: called before reading each record to display (may be called multiple times)
PRE_DRAW: called before each page is displayed
POST_DRAW: called after page has been displayed
SEARCH: called when “Search” button pressed on the report page
Event | Available/Passed-in Parameters | Special Return Information |
---|---|---|
CREATE | CTLENTID=unique report ID | |
SELECT | CTLENTID=list name to create userFields@=rlist info | statMsg@ |
PRE_SEARCH | CTLENTID=name of search section | statMsg@ |
PRE_READ | CTLENTID=@ID bIsNew@ | |
PRE_DRAW | CTLENTID=”DETAILS”/”RESULTS” userFields@=page info | |
POST_DRAW | CTLENTID=”DETAILS”/”RESULTS” userFields@=page info | |
SEARCH | CTLENTID=search button ID userFields@=list info | statMsg@ |
In order to provide additional, and advanced, functionality to your O4W WYSIWYG form, you may choose to create and modify a "commuter module" that the form will invoke. The commuter module generated by the O4W WYSIWYG designer is similar to, but slightly different from, the commuter module generated by the O4W Form Designer.
If you wish to use a commuter module, you must click on the “Edit Form Properties” button on the bottom status bar, or “Overall Form Properties” on the “Edit” menu. On the “Behavior” tab, you _must_ check the "Use Commuter module for form?" checkbox, and enter (or accept) the name of the commuter module. You can then select which events you wish to be sent to the commuter module; choose which "form-level" events you would like to process from the "Behavior" tab, and choose which "field-level" events you would like to process on the “Events” tab of the individual form elements. The form level events include "Pre-Read", "Post-Read", "Pre-Write", "Post-Write", "Pre-Delete", "Post-Delete", "Select", and "Tab". The field level events typically include "Before Entering field", “When Value Changes”, and "When Exiting field", but may be different depending on the element.
[Note that, while the temptation to add in EVERY event for EVERY control is great, this should be avoided if at all possible. While the underlying web technology behind O4W allows for much greater interactivity than the "web 1.0" straight-html forms of a few years ago, the web browser is NOT yet a replacement for the desktop, and attempting to have an O4W WYSIWYG form behave exactly the same as an OpenInsight (desktop-based) form - in terms of handling every event - will probably leave developers and end users alike frustrated. If for no other reason than Internet delays and slowdowns, interacting with the commuter module (which requires a "round trip" from the web browser to the web server, and additional processing time inside of OpenInsight, O4W, and the commuter module itself) can take a noticable time. For more performant forms (and therefore happier users), it is important to only 'capture' those events that need to be handled.]
Whenever the WYSIWYG form is saved (from the File/Save menu), you will be prompted to have O4W automatically create and compile the commuter module "skeleton". If this is the first time through the form definition process for this form with the "Use commuter module for form?" checkbox checked, this will be your only option; if, however, the commuter module may have been created (and manually modified) previously, you are also presented the option of downloading (into Windows notepad) the updated auto-generated commuter module code. You can then manually copy and paste any relevant changes from Windows notepad into your OpenInsight stored procedure.
The automatically generated 'skeleton' of the commuter module will include code for each of the events you have specified (PRE-READ, PRE-WRITE, etc.) in the form definition process. You should then add additional code to perform the desired actions during those events.
The O4W WYSIWYG form, when run, may raise several different "events"; if you have created a commuter module, and choose to have those events passed to your commuter module, you may programmatically alter how O4W behaves.
When the event is raised, and the commuter module is called, it will be passed the name of the control (if applicable) that is generating the event, and the type of event. There are also several "named common" elements that you may access in your commuter module for additional flexibility. These elements include formDef@ (the definition of this O4W WYSIWYG form), headerDef@ (the form “header” information), @ID (if applicable), @RECORD (if applicable), FormName@ (the name of the O4W WYSIWYG form), bIsNew@ (set to "1" during READ processing if this is a new record), and UserFields@ (set to the new and current tab information during TAB processing, and also set during processing of the "POPUP" event).
The commuter module 'skeleton' will also contain code to extract the name of the field that is generating the event (if applicable), and its current value (again, if applicable).
When returning from the commuter module to the O4W WYSIWYG form, the variable "rtnValue" must be set properly to allow, or prevent, the standard form operation for that event from occurring. Set the rtnValue to "1" to allow the event to continue processing (the default behavior), or "0" to halt that event (note that in certain circumstances described below, rtnValue may also be set to "-1" for special operations). There are also additional "named common" elements that may be set to effect the O4W Form. These elements include statMsg@ (set to have the O4W WYSIWYG form display a message), redirectTo@ (set after WRITE or DELETE processing to redirect the browser page), and UserFields@ (set during processing of the "POPUP" event).
The events that are raised, and that may be handled through the commuter module, include:
Form level events:
- CREATE
- READY
- PRE_READ
- POST_READ
- READ_CHECK
- PRE_WRITE
- POST_WRITE
- PRE_DELETE
- POST_DELETE
- SELECT
- POST_DRAW
Field level events vary by control, but may include:
- PRE_FIELD
- POST_FIELD
- CLICK
- TAB
- POPUP
The PRE_READ Event
The PRE_READ event is raised before the O4W WYSIWYG form reads a data record. It is called during two different processes - if there is a 'picklist', or list of possibly matching records returned as the result of a search, the PRE_READ event is raised for EACH record that is displayed in the list of records. In addition, it is called to read the actual data record that is to be displayed or edited in the main body of the form. If the "Add New" button has been pressed, then bIsNew@ is set to "1".
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record.
To continue normal processing, set rtnValue to "1". To abort the read event, set rtnValue to "0" (and optionally set statMsg@ to indicate the reason for the abort). If you wish to perform the same function as the READ event in the commuter module, set the @RECORD variable as desired, and then set rtnValue to "-1" (this instructs the O4W WYSIWYG Form that you wish to skip the actual READ, but wish to continue normal processing beyond that).
If for example you want to read records for a specific user your code in the PRE_READ event could be as follows:
USERNO = "X_UNKOWN_X" @RECORD = "" OPEN 'WEB_USERS' TO USER.FL Else statMsg@ = "Unable to access WEB_USERS table" rtnValue=0 Return rtnValue End OPEN 'REVCONT' TO REVCONT.FL Else statMsg@ = "Unable to access REVCONT table" rtnValue=0 Return rtnValue End TEMPID = O4WCookie("USERID") READ TEMP.INFO FROM O4WTEMPFILE%, TEMPID Then READ USER.ITEM FROM USER.FL, TEMP.INFO<1> Then READ MOEREC FROM REVCONT.FL,USER.ITEM<5> THEN USERNO=MOEREC<1> sno_user = Xlate("SERIALNO_HISTORY", CTLENTID, 2, "X") If USERNO # SNO_USER Then statMsg@="Serial number requested is not for your user ID, please call Revelation Software at (800) 262-4747." rtnValue=0 Return rtnValue End End End End
The POST_READ Event
The POST_READ event is raised after the O4W WYSIWYG Form reads a data record. It is called after the actual data record that is to be displayed or edited in the main body of the form has been read.
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record, and @RECORD for the contents of the data record.
The READ_CHECK Event
The READ_CHECK event is raised before a DELETE or WRITE of the record. It is done as part of the "optimistic locking" algorithm, which seeks to verify that the record has not been changed by another user during web-based processing. Before writing or deleting the record, the original record is re-read, and - if it has not been changed - the record is locked and then updated or deleted. The READ_CHECK event is raised so that the commuter module can recreate any operations performed during the original PRE_READ event (note that this event is automatically added if the PRE_READ event has been selected).
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record.
To continue normal processing, set rtnValue to "1". If you wish to perform the same function as the READ event in the commuter module, set the @RECORD variable as desired, and then set rtnValue to "-1" (this instructs the O4W WYSIWYG Form that you wish to skip the actual READ, but wish to continue normal processing beyond that). Note there is no option to "abort" the READ_CHECK operation by setting rtnValue to "0" - if you wish to abort WRITE or DELETE processing, you must handle that in the PRE_WRITE or PRE_DELETE event, as appropriate.
The PRE_WRITE and PRE_DELETE Events
The PRE_WRITE and PRE_DELETE events are raised before the O4W WYSIWYG Form writes or deletes the data record, respectively.
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record. The @RECORD variable will contain the updated values that are to be written or deleted.
To continue normal processing, set rtnValue to "1". To abort the write or delete event, set rtnValue to "0" or "-1"(and optionally set statMsg@ to indicate the reason for the abort). If set to "-1", the O4W WYSIWYG Form will return to the main entry/selection page; if set to "0", the current page remains unchanged.
The POST_WRITE and POST_DELETE Events
The POST_WRITE and POST_DELETE events are raised after the O4W WYSIWYG Form successfully writes or deletes the data record, respectively.
The commuter module code may examine either @ID, or the passed "CtlEntId" parameter, for the name of the record. The @RECORD variable will contain the updated values that have been written or deleted.
Optionally set statMsg@ to any message you wish to display. Optionally set redirectTo@ to the URL of any page you wish to transfer to; if not specified, the O4W WYSIWYG Form will return to the main entry/selection page. Note that the value of the rtnValue variable is not applicable here.
The SELECT Event
The SELECT event is raised when generating the list of keys to choose from in a "picklist" type O4W WYSIWYG Form, or when generating the search results in a "search" O4W WYSIWYG Form.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the list record that is to be created. The list record is an entry in the SYSLISTS table that contains a list of all the IDs that the user can select from.
If rtnValue is set to "1", normal processing continues. If rtnValue is set to "0", NO records are selected. If rtnValue is set to "-1", the O4W WYSIWYG Form will proceed with whatever keys have been placed into the list record without using its own algorithm to select the keys. In this way, the commuter module can use its own logic, overriding the built-in O4W WYSIWYG Form search/selection logic.
For example if you wanted to change the sort order of your “picklist” you can insert the following code on the SELECT event:
cmd = "SELECT ROYSCHED BY TITLE_ID BY LORANGE" Call Rlist(cmd, 4, CtlEntId) rtnValue = -1
The TAB Event
The TAB event is raised when the user clicks on any tab of the O4W WYSIWYG Form, or uses the optional Back or Next buttons to navigate through the tabs.
The commuter module code may examine the passed "CtlEntId" parameter for the element ID of the current tab, and the UserFields@ variable for the number of the tab the user wishes to move to (found in field 1) and the tab number they are moving from (found in field 2). Note that the commuter module 'skeleton' code will translate the current tab number into a tab name and the element ID of that tab, available in the variables "currenttabname" and “currenttabID” respectively.
If rtnValue is set to "1", normal processing continues. If rtnValue is set to "0", the request to switch tabs is aborted; any message stored in statMsg@ will be displayed.
The PRE_FIELD Event
The PRE_FIELD event is raised when the specified field is about to get the "focus" on the web page.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page input control that is about to get the focus. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W WYSIWYG form definition, available in the variable "fieldName".
If statMsg@ is set, its contents are displayed. Note that the value of the rtnValue variable is not applicable here.
The POST_FIELD Event
The POST_FIELD event is raised when the specified field is about to lose the "focus" on the web page.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page input control that is about to lose the focus. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W WYSIWYG form definition, available in the variable "fieldName". In addition, the current value of the field is available in the variable "currValue".
If statMsg@ is set, its contents are displayed. If rtnValue is set to "0", focus is re-set back to this field (so the user is unable to exit this input control). Note that MANY users find this action extremely annoying, and it should thus be used only when absolutely required.
Never set the statMsg@ and rtnValue = -1 at the same time. This generates an infinite loop (since the post_field is triggered by the ‘lost focus’ event, and you get into a loop where the form loses focus, you go into the commuter module, you set rtnValue = -1 (which sets us back to that same field), then you set statMsg@ (which puts up a message box)…which maes us lose focus on the field – and son on).
The CLICK Event
The CLICK event is raised when the specified element is clicked.
The commuter module code may examine the passed "CtlEntId" parameter for the name of the web page element that has been pressed. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W WYSIWYG form definition, available in the variable "fieldName".
Note that the values of the rtnValue and statMsg@ variables are not applicable here.
The POPUP Event
The POPUP event is raised when specified "popup" field has returned a value.
The commuter module code may examine the passed "CtlEntId" parameter for the element ID of the web page that is the target for this popup. Note that the commuter module 'skeleton' code will translate the web page input control name into the name of the field as specified in the O4W WYSIWYG form definition, available in the variable "fieldName". The userFields@ variable will contain the return value from the popup.
The commuter module can change what value is placed into the associated field by changing the value of the userFields@ variable. The field where the popup value is to be placed can also be changed by changing the value of the "CtlEntId" variable.
If statMsg@ is not null, any text specified in statMsg@ is displayed. If rtnValue is set to "1", normal processing continues. If rtnValue is set to "0", the returned value from the popup is NOT placed in the associated field.
Once the commuter module has been invoked for a particular event, it most likely will need to access, and potentially update, elements of the browser page.
When the O4W WYSIWYG Form was defined, fields from the specific table's dictionary, and any user-defined fields, were selected for use as "input" controls (textboxes, password boxes, etc.), and each input element has a “name” value associated with it. When the form is actually created for the web browser, each input control is given a unique name, based on the name that was specified for the element.
The commuter module utility function O4WCommuterUtility has been developed to provide the "conversions" from field names to web form elements. To find which field name is associated with the current input control, we might have code like the following:
MyControlName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$)
To find what input control name is used for a particular field name, we need to determine the location of that field name in the list of all field names used by the form. To find the input control name for field "ELEMENT_27”, for example, we could use the following code:
stateElement = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "ELEMENT_27")
An additional complication is multivalue fields, and associated multivalue sets that may increase or decrease in number. The O4WCommuterUtility will use the information passed in with the current control (via the parameter CtlEntId) to determine the proper value to extract from the current field:
currValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$)
If you wish to extract information from another field, simply specify that field name:
otherValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$, "ELEMENT_27”)
If the additional field (in this case, "ELEMENT_27") is part of a multivalue association with the current field, or neither the current field nor the additional field are multivalued, you should pass in the CtlEntId variable as the first parameter; however, if the current field is multivalued and the additional field is not multivalued, or the two fields are part of different associations, you should pass in a null string ("") for the first parameter to prevent the incorrect value from being retrieved.
You can also use O4WCommuterUtility to return the names of other web page elements that you might wish to manipulate. The O4W WYSIWYG Form is laid out as a set of 'sections'; each element of the form is called a "section" (or, for backwards compatibility, a “cell”), and can have specific properties defined (like background color, alignment, etc.). Each field on the O4W WYSIWYG Form may contain one (or more, for multivalued data) "input controls", plain text, images, or other elements. Depending on what you wish to change, you can access:
- The "section” or “cell" of the grid that contains the input control, with the O4WUTILITY_FORMELEMENT_CONTROL_SECTION$ or O4WUTILITY_FORMELEMENT_CONTROL_CELL$ parameter;
- The “section” or "cell" of the grid that contains the label associated with this element (if any), with the O4WUTILITY_FORMELEMENT_LABEL_SECTION$ or O4WUTILITY_FORMELEMENT_LABEL_CELL$ parameter;
- The input control itself, with the O4WUTILITY_FORMELEMENT_CONTROL$ parameter;
- The label element associated with this element (if any) with the O4WUTILITY_FORMELEMENT_LABEL$ parameter;
- The label element(s) that are to be updated when the current field’s value changed with the O4WUTILITY_FORMELEMENT_TEXT$ parameter
The following code was generated as the "skeleton" commuter module for an O4W WYSIWYG Form named "CUSTOMER". The Pre-read, Post-write, and Tab form-level events have been selected, and there are a number of field-level events (including Pre-field and Post-field on a number of fields, a "dummy" popup button, and a button).
FUNCTION O4WCM2_CUSTOMER (CtlEntId, Event, Request) * Auto-generated by O4W_DESIGN_FORM at 10:51:51 06 NOV 2016 * Standard equates $Insert O4WEquates $Insert O4WFormEquates $Insert O4W_COMMUTER_COMMON rtnValue = 1 Begin Case Case event _eqc 'PRE_READ' * called before reading record from table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * examine variable bIsNew@ to determine if this is a new record, or a record that is supposed to exist * set variable 'rtnValue' to 0 (rtnValue=0)to disable further event processing * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should: * - fill the @RECORD variable with the desired record contents * - fill the userFields@ variable with the desired values for the user-defined fields * ) * set variable 'statMsg@' to desired error/status text Case event _eqc 'READ_CHECK' * called before write or delete to verify record has not been changed by another user * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should fill the * @RECORD variable with the desired record contents) Case event _eqc 'POST_WRITE' * called after writing record to table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * set variable 'statMsg@' to any desired 'success' message * set variable 'redirectTo@' to url to transfer to after success Case event _eqc 'TAB' * called when 'tab' clicked, or back/next button clicked * variable 'ctlentid' is number of current tab tabname = formDef@<tabnames$, ctlentid> * variable userFields@ is number of 'next' tab * set variable 'rtnValue' to 0 (rtnValue=0) to abort tab change * set variable 'statMsg@' to any desired error/status text * to examine all the fields on the current tab: num.fields = dcount(formDef@<attr$>, @VM) for each.field = 1 to num.fields if formDef@<ValueTab$, each.field> = tabname then fieldName = formDef@<attr$, each.field> thisValue = O4WGetValue('FIELD_':each.field) end next each.field Case Event _eqc 'PRE_FIELD' * called when specified field has gotten 'focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) Begin Case Case fieldName _eqc 'WEBSITE' End Case Case Event _eqc 'POST_FIELD' * called when specified field has 'lost focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) currValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$) Begin Case Case fieldName _eqc 'WEBSITE' Case fieldName _eqc 'ADDRESS1' Case fieldName _eqc 'CITY' End Case Case Event _eqc 'CLICK' * called when user-defined or 'dummy' popup button clicked fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) clickType = O4WCommuterUtility(CtlEntId, O4WUTILITY_CONTROLTYPE$) Begin Case Case fieldName _eqc 'GROUP' * called from clicking 'popup' button Case fieldName _eqc 'SampleBtn' * called from clicking user-defined button End Case End Case Return rtnValue
This is the code that is built by O4W without any modification. At this point, the code can be modified (using both "regular" BASIC+ programming code, and/or O4W APIs).
Example 1: Setting A Field's Value Based On Another Field
Let us suppose, for example, that we have an OpenInsight stored procedure that, when given the name of the city, can tell us the state that city is in. Let us set our STATE field's value based on the value of CITY. When the CITY field has lost "focus", we can populate the STATE field with our calculated result.
Therefore, in the "POST_FIELD" section of the skeleton code, we look for the 'CITY' case. We can add the following code:
Call FindStateFromCity(currValue, ourState) * Determine the web form's "input element" name for the STATE field stField = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "STATE") * and update that element O4WUpdate(stField, ourState, O4WResponseStyle(%%''%%,'1'))
Assuming the FindStateFromCity stored procedure returns (in the second parameter) the name of the state based on the city (in the first parameter), we just take the returned value and put it into the web form where the "STATE" field is.
As described above, we need to translate from the field name ("STATE") to the input control name; to do this, we use O4WCommuterUtility to find STATE's location in the list of fields (or, when designing the O4W WYSIWYG form, you can directly note the element ID of the desired controls).
Once we have determined the input control name, we use the O4W API call O4WUpdate to set that control's text to the value in the "ourState" variable. We use the O4WResponseStyle call to indicate that we only wish to update the text (and not the style) of the input control.
Note: The O4W WYSIWYG Form Designer also allows you to specify “related controls” that are updated when a particular control’s value has changed. In the example above, if the value for the state is derived in a calculated field, you can specify that changes to the city element should automatically trigger an update to the displayed state’s value. This can be done without any programming in the commuter module.
Example 2: Generating a POST-WRITE Redirect
Let us suppose that, after writing the record, we wish to display a specific message, and then transfer control to the Revelation home page (rather than back the start of the O4W WYSIWYG Form). To achieve this, we must modify the code in the POST_WRITE case as follows:
redirectTo@ = "http:%%//%%www.revelation.com" statMsg@ = "Thank you for updating this record! You will now be redirected to the Revelation Home Page..."
Example 3: Aborting Form Read
As an example, perhaps we wish to forbid reading of records on the same day they were created. In the Pre-read event, we can check if the requested record is 'forbidden', and if it is, abort the read (with an appropriate error message). To achieve this, in the PRE_READ case, we can add the following code:
if bIsNew@ <> "1" then * Not a new record - find out its creation date createDate = xlate("CUSTOMER", @ID, 27, "X") If createDate = DATE() Then rtnValue = 0 statMsg@ = "Invalid record requested; not yet posted" End End
Example 4: Acting On A Button
To display a static message (using the O4WError API call) when a button is clicked, we can add the following code to the "SampleBtn" case:
O4WError("This button intentionally left blank")
Example 5: Updating A ListBox
To update a more complex control, like a listbox, defined as the "input control" for another field, we must first derive the "descriptions" and their associated "code values" that will be placed in the list box. We must then replace the list box control contents entirely; we do that by actually replacing the "section" where that control is located. For example, if we wished to populate a list box in the “ADDRESS2" field (for example, ELEMENT_12) when we exit the "ADDRESS1" field, we can add the following code to the "ADDRESS1" case in the "POST_FIELD" section:
* Change the 'address2' list box Call findSubAddress(currValue, DESCS, CODES) add2Control = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "ELEMENT_12") add2Section = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL_SECTION$, "ELEMENT_12") o4wSectionStart(add2Section, O4WResponseStyle()) O4WListbox(DESCS, CODES, add2Control) O4WSectionEnd(add2Section)
Assuming the findSubAddress stored procedure returns (in the second and third parameters) a list of descriptions (@VM delimited), and their associated code values, for the secondary address based on the primary address (in the first parameter), we can take the returned values and put them into the web form where the "ADDRESS2" field is.
As described above, we need to translate from the field name ("ELEMENT_12”) to the input control name; to do this, we use O4WCommuterUtility to find ELEMENT_12's location in the list of fields. We also use O4WCommuterUtility to retrieve the name of the "section" where ELEMENT_12's input control is located.
Once we have determined the input control name, and the section where it is defined, we use the O4W API calls O4WSectionStart, O4WListBox, and O4WSectionEnd to effectively recreate that very small section of the browser's page. The O4WResponseStyle modifier, invoked with no parameters, indicates that we will be replacing the entire element (text, style, attributes, etc.). Once we have recreated the section, we create the new list box, and our new descriptions are now available.
Completed, Modified Commuter Module
FUNCTION O4WCM2_CUSTOMER(CtlEntId, Event, Request) * Auto-generated by O4W_DESIGN_FORM at 10:51:51 06 NOV 2016 * Standard equates $Insert O4WEquates $Insert O4WFormEquates $Insert O4W_COMMUTER_COMMON rtnValue = 1 Begin Case Case event _eqc 'PRE_READ' * called before reading record from table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * examine variable bIsNew@ to determine if this is a new record, or a record that is supposed to exist * set variable 'rtnValue' to 0 (rtnValue=0)to disable further event processing * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should: * - fill the @RECORD variable with the desired record contents * - fill the userFields@ variable with the desired values for the user-defined fields * ) * set variable 'statMsg@' to desired error/status text if bIsNew@ <> "1" then * Not a new record - find out its creation date createDate = xlate("CUSTOMER", @ID, 27, "X") If createDate = DATE() Then rtnValue = 0 statMsg@ = "Invalid record requested; not yet posted" End End Case event _eqc 'READ_CHECK' * called before write or delete to verify record has not been changed by another user * set variable 'rtnValue' to -1 (rtnValue=-1) to skip READ but continue processing * (If performing the read in this code, and you intend to set the rtnValue=-1, you should fill the * @RECORD variable with the desired record contents) Case event _eqc 'POST_WRITE' * called after writing record to table * variable 'ctlentid' is ID of record * variable '@record' is the record contents (if available) * variable 'userFields@' contains the values of the user-defined fields (if available) * set variable 'statMsg@' to any desired 'success' message * set variable 'redirectTo@' to url to transfer to after success redirectTo@ = "http:%%//%%www.revelation.com" statMsg@ = "Thank you for updating this record! You will now be redirected to the Revelation Home Page..." Case event _eqc 'TAB' * called when 'tab' clicked, or back/next button clicked * variable 'ctlentid' is number of current tab tabname = formDef@<tabnames$, ctlentid> * variable userFields@ is number of 'next' tab * set variable 'rtnValue' to 0 (rtnValue=0) to abort tab change * set variable 'statMsg@' to any desired error/status text * to examine all the fields on the current tab: num.fields = dcount(formDef@<attr$>, @VM) for each.field = 1 to num.fields if formDef@<ValueTab$, each.field> = tabname then fieldName = formDef@<attr$, each.field> thisValue = O4WGetValue('FIELD_':each.field) end next each.field Case Event _eqc 'PRE_FIELD' * called when specified field has gotten 'focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) Begin Case Case fieldName _eqc 'WEBSITE' End Case Case Event _eqc 'POST_FIELD' * called when specified field has 'lost focus' fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) currValue = O4WCommuterUtility(CtlEntId, O4WUTILITY_VALUE$) Begin Case Case fieldName _eqc 'WEBSITE' Case fieldName _eqc 'ADDRESS1' * Change the 'address2' list box Call findSubAddress(currValue, DESCS, CODES) add2Control = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "ADDRESS2") add2Section = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL_SECTION$, "ADDRESS2") o4wSectionStart(add2Section, O4WResponseStyle()) O4WListbox(DESCS, CODES, add2Control) O4WSectionEnd(add2Section) Case fieldName _eqc 'CITY' Call FindStateFromCity(currValue, ourState) * Determine the web form's "input element" name for the STATE field stField = O4WCommuterUtility(CtlEntId, O4WUTILITY_FORMELEMENT_CONTROL$, "STATE") O4WUpdate(stField, ourState, O4WResponseStyle(%%''%%,'1')) End Case Case Event _eqc 'CLICK' * called when user-defined or 'dummy' popup button clicked fieldName = O4WCommuterUtility(CtlEntId, O4WUTILITY_FIELDNAME$) clickType = O4WCommuterUtility(CtlEntId, O4WUTILITY_CONTROLTYPE$) Begin Case Case fieldName _eqc 'GROUP' * called from clicking 'popup' button Case fieldName _eqc 'SampleBtn' * called from clicking user-defined button O4WError("This button intentionally left blank") End Case End Case Return rtnValue
Form-level events (in order of occurrence):
CREATE: called when the form is first invoked (via link, bookmark, etc.)
SELECT: called with information needed to select one or more records
PRE_READ: called before reading specific record
POST_DRAW: called after all fields have been drawn
POST_READ: called after all fields have been populated
[Field-level events occur while the user navigates around the page; see below for details]
READ_CHECK: called to validate the underlying record has not changed
PRE_WRITE/PRE_DELETE: called before saving or deleting the record
POST_WRITE/POST_DELETE: called after saving or deleting the record
Field-level events (each may occur multiple times/form):
- TAB: called when the user clicks on a tab
- (in order of occurrence)
PRE_FIELD: called when a field has just received the focus
CHANGE: called when the field’s value has changed
POST_FIELD: called when a field has lost focus
- CLICK: called for a button or popup
- POPUP: called when a popup has returned
Event | Available/Passed-in Parameters | Special Return Information |
---|---|---|
CREATE | CTLENTID=unique form ID | |
SELECT | CTLENTID=list name to create | |
PRE_READ | CTLENTID=@ID bIsNew@ | statMsg@ |
POST_DRAW | bIsNew@ | statMsg@ |
POST_READ | CTLENTID=@ID @RECORD | |
READ_CHECK | CTLENTID=@ID bIsNew@ | statMsg@ |
PRE_WRITE | CTLENTID=@ID @RECORD | statMsg@ |
PRE_DELETE | CTLENTID=@ID @RECORD | statMsg@ |
POST_WRITE | CTLENTID=@ID @RECORD | statMsg@ redirectTo@ |
POST_DELETE | CTLENTID=@ID @RECORD | statMsg@ redirectTo@ |
TAB | CTLENTID=current tab element ID userFields@=new tab # ^ old tab # | |
PRE_FIELD | CTLENTID=control causing event FIELDNAME=name of field | |
CHANGE | CTLENTID=control causing event FIELDNAME=name of field CURRVALUE=current field value | |
POST_FIELD | CTLENTID=control causing event FIELDNAME=name of field CURRVALUE=current field value | |
CLICK | CTLENTID=control causing event FIELDNAME=name of field CLICKTYPE=type of field | |
POPUP | CTLENTID=control to receive result FIELDNAME=name of destination field userFields@=popup results | statMsg@ userFields@=popup results |
Label | Action Parameter | Generates Value Of Type: |
---|---|---|
1 | O4WUTILITY_FORMELEMENT_LABEL_CELL$ | id=”ELEMENT_38_content” |
2 | O4WUTILITY_FORMELEMENT_CONTROL_CELL$ | id=”ELEMENT_37_content” |
3 | O4WUTILITY_FORMELEMENT_LABEL$ | id=”ELEMENT_38” |
4 | O4WUTILITY_FORMELEMENT_CONTROL$ | id=”ELEMENT_37” |
5 | O4WUTILITY_FORMELEMENT_TEXT$ | Id=”ELEMENT_39” (not shown) |
Other O4WCommuterUtility Values:
Action Parameter | Returns |
---|---|
O4WUTILITY_FieldName$ | name of field given control |
O4WUTILITY_Value$ | value in field (if available) |
O4WUTILITY_TableName$ | name of table containing given control |
On a multi-tab O4W WYSIWYG Form-wizard designed form, you might occasionally wish to programmatically disable one or more tabs (for example, certain tabs might contain information that's not appropriate or relevant for the current record). You can use the commuter module and the "tabs" plugin to achieve this.
Unfortunately, there's no call in the commuterutility to tell us "real time" what all the tabs are, so we have to hard-code which tabs you'll want to disable or enable - so this means that if you go in and add a new tab, you'll need to remember to update your commuter module logic.
So, let's say that you want to programmatically disable the second and fourth tabs. Each tab has a tab number, starting from 0, so the second and fourth tabs are really tab #1 and #3. We can put the following code into the 'post-write' event:
If bDoDisableTabs = "1" then action = "disabled" End Else action = "enabled" End Command = '"option","':action:'",[1,3]'
* The following line invokes the 'tabs' plugin on our tab element, for example ELEMENT_5
* It passes in the command line that we've just built, which is something like "option","disabled",[1,3] or "option","enabled",[1,3]
O4WPlugin("ELEMENT_5", 'tabs', command)
The only complex bit is the line that builds the "command" string that we pass into the plugin. It reads
Singlequote doublequote option doublequote comma doublequote singlequote colon action colon singlequote doublequote comma bracket 1 comma 3 bracket singlequote
If we only need to enable or disable a single tab, then instead of putting the tab numbers inside braces, we can just pass in that single tab number, so if we just wanted to disable or enable the fourth tab (instead of both the second and fourth tabs), the Command code would look like this:
Command = '"option","':action:'",3'
On an O4W WYSIWYG form, you might occasionally wish to programmatically show or hide one or more "groups" that you've defined in form definition process (for example, certain groups might contain information that's not appropriate or relevant for the current record). You can use the commuter module and O4WQualifyEvent to achieve this.
In order to implement this functionality, you must know the element ID of the “container element” (for example, a section) that contains the group. Then, we can show or hide the entire section, as appropriate.
* put this in the "post draw" event in the commuter module * use the element ID of the section that contains the desired fields if bHideSection = "1" then O4WQualifyEvent("", "hide", “ELEMENT_10”) ;* we use "" in the first parameter to tell the qualify event we want the hide to take effect immediately End else O4WQualifyEvent("", "show", “ELEMENT_10”) ;* we use "" in the first parameter to tell the qualify event we want the show to take effect immediately End