3.5X Helium Tech Reference
This document is intended to help technical resources develop eForm solutions that use the new GT eForms 3.50 "Helium" form engine. This new engine significantly improves performance for users by only doing what needs to be done by managing dependencies between form type configuration and code elements. It also supports subscription code patterns that can simply eForm solutions.
GT eForms 3.50 also includes the prior GT eForms 3.30.04 form engine, referred to as the "Standard" form engine. eForms developed in 3.30.x can be configured to use the Standard engine to work as they did before the 3.50 upgrade without any refactoring.
This document also provides tips to refactor a Standard form into a Helium form. These are presented throughout the document with a gray background, primarily in the code pattern sections. If you are not refactoring an existing eForm this can be ignored. While this document should cover a majority of the refactor patterns, it is not a comprehensive list of what needs to be refactored to get a form working in Helium.
This is a living document and will likely be updated in the future with additional information. As always, please contact support@gideontaylor.com for additional assistance.
Conceptual Overview
There are new concepts to understand when developing a Helium eForm. Understanding these concepts and using the code patterns in the next section will help you develop eForm solutions that take advantage of the efficiency and simplicity the Helium engine enables.
Standard-To-Helium Conversion
If you plan to convert any form types from Standard to Helium, we recommend that you first upgrade to 3.58.04
and make a plan to convert any existing form transactions to Helium as well using the Standard Form Data Converter Utility. Read the documentation to learn more about this tool.
Config over Code
Helium continues adding to the configuration capabilities of GT eForms. It is highly recommended that you become familiar with the patterns in this document and what can be configured using GT eForms. If something can be accomplished using configuration, it almost always should be. Examples of this are:
- A field value should rarely be set in code when it can be lifecycled using configuration
- A field should rarely be hidden in code when it can be hidden using a Visual If
- Code should rarely be used to prevent access to a form when the eForm search or a participation roster could meet the need
- Calculating a total for a grid should be done using a reusable PPC SmartSource rather than putting all the logic in field change, row insert, and row delete PPC hooks. (See Annotations)
There are several benefits to using configuration over code:
- Role plasticity - the task can be performed and maintained by both functional and technical resources
- Visibility – functional and technical resources can easily see the logic behind the behavior. The Helium Network Visualizer also helps visualize the form logic when using configuration and PPC parts.
- Flexibility – it is easier to change the logic using configuration
- Mobility – it is easier to move or replicate manually in another environment
- Deployability – it is easier to deploy a configured eForm to end users using GT participation rosters and the deployment tool
Subscriptions/Dependencies
Imagine a simple form where Field B is visible if Field A has a value. Field B is dependent on Field A (A -> B). In other words, Field B is subscribed to or watching Field A. If A changes, Field B will respond.
The terms subscription and dependency are equivalent. The actual path for this scenario in the Dependency Network is more involved but the concept of one node subscribing/depending on another is the same:
FieldTag
A > SmartSource A > Visual If Part > Visual If > ColumnSegmentField
B
When a user changes Field A, they are changing the value on the FieldTag
(the data representation of the field). All form fields are available as SmartSources. SmartSource A subscribes to the value of FieldTag
A. Continuing down the traversal path results in the ColumnSegmentField
B (the display representation of a field) repainting the field as visible or hidden.
The actual traversal of this path is more nuanced, but conceptually all the nodes in the path end up being resolved. There are a variety of other types of dependencies (or connection types) between nodes in addition to the propagation of data. See the Network Visualizer document for more information about node types, connection types, and how to use the Network Visualizer to analyze and understand how elements of a Form Type are connected.
Annotations
Some PPC Parts and Hooks are resolved in the Standard form engine with every user interaction. The Helium form engine is smarter and only resolves what it needs to, based on the Dependency Network. As a result, some PPC Parts and Hooks must communicate to the Form Type Build what they depend on. In other words, those PPC Parts and Hooks will not be run/resolved when changes happen on the form if they are not coded to be dependent on those changes.
This communication happens using Annotations. Annotations are comments directly above a PPC Part or Hook method that follow this pattern:
/*@Dependency(Type="<Type>", Value="<ID>")*/
The GT annotation pattern is modeled after dependency injection annotations in Java: (jcp.org) Dependency Injection For Java
SmartSource
If a solution PPC Hook or Part needs to re-run when a form field changes, add a SmartSource annotation using the form field’s SmartSource. This is the most common use of annotations.
/*@Dependency(Type="SmartSource", Value="[PAGREREC:SOMEFIELD]")*/
Visual If
If a solution PPC Hook or Part needs to re-run when a Visual If value changes, use a Visual If annotation using the Visual If id. This is useful if a developer wants to branch logic in a Hook or Part based on the result of a previously configured Visual If. This is a less common use of annotations.
/*@Dependency(Type="VisualIf", Value="897167cc-8126-11eb-bc85-85af5be90c30")*/
Tip: The Network Visualizer can be used to find the Visual If id. You can then select Node Type: VisualIf and then inspect the Node field element to copy the Visual If id.
RowsetTag
If a solution PPC Hook or Part depends on a grid and needs to re-run when a row is added, deleted, or a grid value is changed, add a RowsetTag
annotation using the grid segment’s RowsetTag
.
/*@Dependency(Type="RowsetTag", Value="GRID02")*/
This is common when Hooks or Parts need to subscribe to one of the following:
- The number of rows in a grid (e.g. a Visual If Part that returns true if a grid has 3 or more rows)
- If a row or multiple rows in a grid meet certain criteria (e.g. a Visual If Part that returns true if a grid has two rows over $50)
- Calculating a value from grid data (e.g. a SmartSource that returns a total of the salaries for all full-time employees in a grid)
Example:
A common use case for a RowsetTag
annotation is to update a column segment Total field with the sum of the Amount fields in a grid. To accomplish this, set the Total field to lifecycle ("Update when this Source changes") from a PPC SmartSource that iterates through the grid Amount values to calculate and return the total. Then above the PPC SmartSource method, add a SmartSource annotation to the Total field’s SmartSource and add a RowsetTag
annotation to the grid segment’s RowsetTag
.
/*@Dependency(Type="SmartSource", Value="[ROWCOUNT:AMOUNT]")*/
/*@Dependency(Type="RowsetTag", Value="ROWCOUNT")*/
method SMARTSRC_SGTotalAmount
/+ Returns Number +/
Local G3FORM:TAGS:RowsetTag &gridRS;
&gridRS = &G3FRM.rowset("ROWCOUNT");
Local integer &i;
Local number &total;
If All(&gridRS) Then
For &i = 1 To &gridRS.ActiveRowCount
&total = &total + &gridRS.row(&i).record("ROWCOUNT").field("AMOUNT").Value;
End-For;
End-If;
Return &total;
end-method;
After building the Form Type, the S G Total Amount
SmartSource will then be available to select in the "Update when This Source Changes" field for the Total Amount field. After setting that value, build the Form Type again and the Total Amount will be updated any time the grid Amount field is updated, or a row is deleted from the grid.
This is what the Network Visualizer will look like for this scenario:
Complex Example:
An eForm has an "Expense" grid to collect travel reimbursement expenses. When the user enters an expense category on a row, a "Reimbursable" checkbox on the row is checked or unchecked. A second "Reimbursement" segment is visible if any rows in the grid are reimbursable.
A PPC Visual If part used in the Visual If on the "Reimbursement" segment would be annotated to look at both the "Expense" grid RowsetTag
and the "Reimbursable" FieldTag
's SmartSource. This annotation is necessary because the value could change if the user adds a row, deletes a row, or changes something that changes the "Reimbursable" value on one of the rows.
When writing PeopleCode for a PPC SmartSource or Visual If Part that depends on a grid rowset and a grid field that is updated from another field on the row, be sure to loop through all the rows. A break statement in the loop could result in unexpected behavior because it prevents the Helium form engine from getting to the row that was changed and propagating the data changes to other dependencies. In this example, the Break statement in red below could possibly prevent the GSREIMBURS value from being set to Y in addition to preventing the Reimbursable segment from showing or hiding correctly.
/*@Dependency(Type="SmartSource", Value="[EXPGRID:GSREIMBURS]")*/
/*@Dependency(Type="RowsetTag", Value="EXPGRID")*/
method VISIF_HasReimbursableExpense
/+ Returns Boolean +/
Local G3FORM:TAGS:RowsetTag &expenseRS = &G3FRM.rowset("EXPGRID");
Local integer &i;
Local boolean &result;
If &gridRS.row(&i).record("EXPGRID").field("GSREIMBURS").Value = "Y" then
&result = True;
Break;
End-For;
Return &result;
end-method;
Form Type Build
The Helium engine uses dependency management to run only what is necessary as a user interacts with a form. This requires building a dependency network with nodes for the configuration and code elements in a Form Type along with connections/edges for the relationships between those elements. Traversal algorithms are applied to the dependency network to calculate exactly what needs to occur when the form is started, changed, and saved. This is all accomplished using a new Form Type Build process. See the Form Type Build document for more information.
Configuration
Nodes that represent configuration in the Form Type Setup and connections between those nodes are added to the Dependency Network without any additional work. The Helium Form Type Build should be run after making configuration changes in the Form Type Setup that affect form data and display. Workflow, notification, and report changes do not require a Form Type Build.
Dependency Managed Code
PPC Hook nodes and connections to configured nodes are added to the Dependency Network as part of the Form Type Build. PPC Parts are added to the Dependency Network as part of the Form Type Build after they are coded and used in the Form Type Setup. Some PPC Parts and Hooks can be annotated in the code to create dependencies on other nodes.
Dependency Managed PPC Parts
-
PPC Smart Source *
-
PPC Visual If *
-
Data Pool Custom Load
-
Custom Segment Driver
Dependency Managed PPC Hooks
-
Field Change
-
Field Edit
-
Page Paint *
-
Segment Paint *
-
Segment Field Paint *
-
Page
PostNav
-
Segment
PostNav
-
Page
PreNav
-
Segment
PreNav
-
SetSortDropdownList
* -
SavePreChange
* Supports Annotations to indicate their execution has a dependency on an element in the form
Helium | Standard (Included for reference/comparison) | |
---|---|---|
After creating a new PPC Part | Form Type Build | Load Custom Parts |
After creating a new PPC Hook | Form Type Build | No Process |
After making changes to a PPC Part | Form Type Build, only if adding, changing, or removing an annotation. | No Process |
After making changes to a PPC Hook | Form Type Build, only if adding, changing, or removing an annotation. | No Process |
After removing a PPC Part | Form Type Build Note: the part definition DOES get deleted from the part tables | No Process Note: the part definition doesn’t get deleted and is still available in lookups. It needs to be deleted from the part tables using SQL |
After removing a PPC Hook | Form Type Build | No Process |
Code Dependencies using PSPCMTXT
The Form Build process parses PeopleCode in the PSPCMTXT
table to identify code dependencies for the dependency network. This means the PeopleCode in the Form App Package Hierarchy (Form Type, Form Family, Search Set, and System App Packages) must have entries in the PSPCMTXT
table. Occasionally, the PSPCMTXT
table can get out of sync with the PeopleCode definitions in PSPCMPROG
table. If this does happen for form solution code, you may see this error in the Form Build process ⬇️
This class doesn’t have any peoplecode in the PSPCMTXT table: <Class Path> (0,0)
G3PPC.Class.OnExecute Name:loadPPC PCPC:8688 Statement:114
Called from:G3DEPBLD.PPCRegistry.FieldEvents.OnExecute Name:findParts Statement:33
Called from:G3DEPBLD.PPCRegistry.Field…
We have come up with an easy process to identify and get the PSPCMTXT
table data in sync with the PSPCMPROG
table in both the current environment and the environments in the migration path to production. All environments that the Form Type will be built in and used will need the form solution code to be in sync between the PSPCMTXT
and PSPCMPROG
tables.
- The first step is to find what is out of sync. Run the following SQL statement to identify which PeopleCode entries in
PSPCMPROG
are not present inPSPCMTXT
:
SELECT DISTINCT OBJECTVALUE1, OBJECTVALUE2, OBJECTVALUE3 FROM PSPCMPROG
WHERE OBJECTVALUE1 LIKE 'G\_%' ESCAPE '\'
MINUS
SELECT DISTINCT OBJECTVALUE1, OBJECTVALUE2, OBJECTVALUE3 FROM PSPCMTXT
WHERE OBJECTVALUE1 LIKE 'G\_%' ESCAPE '\';
This will include all G\_
objects (form solution code). Change the WHERE
criteria as needed.
-
The resulting list will indicate which object need to be resynced. OBJECTVALUE1 is the application package for the missing PeopleCode.
-
To resync, create a new project in Application Designer.
-
Next, add the application package and at least one PeopleCode instance from the application package (only one is necessary to sync the entire application package) corresponding to each OBJECTVALUE1 from the results.
-
After the application packages and PeopleCode instances have been added to the project, export the project to a file.
-
Import the project from a file into either the current environment or a target environment
-
Import the project from the file
-
Check the box to use the project definition from file and hit OK (if re-importing into the current environment)
-
Check the box in the bottom left corner that says "Compile PeopleCode after Import" and then hit Copy
-
-
After the import/compile completes, the
PSPCMTXT
table will be in sync for the Application Packages in the project.
Aside from this method, there are two other approaches we have found to get it in sync.
- Run the
UPGPTHASH
App Engine – this will sync all PPC in the environment betweenPSPCMPROG
andPSPCMTXT
. If it does not appear within the PIA, it can be run directly from App Designer - Open the class in app designer, add a space and save
Please see these links for more information:
- (Oracle Support) Application Designer "Find In" Feature Not Working Consistently
- (Oracle Support) How to extract Peoplecode as text from peopletools table PSPCMPROG%3F
Dependency Managed Framework and Hook Flow
User interactions with a form trigger form events. Solution hooks are available on several of these events to allow for custom logic specific to a form. See Appendix 1: Event/Hook Flow to see the order and potential uses for solution hooks.
Helium Debugging
In Helium, the data and display of a form are controlled by traversals through the Dependency Network. To assist with debugging solution form issues, the Debug Level "11 DEP" has been added to the GT Dev Debug tool. This debug level captures the debug messages generated in the process of traversing the Dependency Network and shows how nodes in the network are resolved.
Helpful Tips
- Download the log to produce an Excel document as the log is comma delimited.
- Turn on filtering for the columns for ease in narrowing down the focus.
- Freeze the top row for ease in navigating the Excel document.
Log Data Columns
DebugLevel – the log level passed to the \&GDBG.Log
method (e.g., \&GDBG._DEBUG
).
DepLevel – the level being traversed. This gets incremented when resolving a node spawns another traversal (e.g. FieldChange
event).
Trigger Node – the node that triggers the traversal. This will be blank if the traversal is not triggered by a node (e.g. FormLoad event).
Event – the event being triggered by the Trigger Node (e.g. PostNav, FieldChange
).
Traversal Node – the node being traversed in the Event (e.g. the field being changed).
Current Node – the node being resolved. The Traversal Node depends on the result of this node’s resolution (e.g. Smart Source, Data Pool Prompt).
Context – the PeopleCode firing the debug message.
Debug Message – the message passed to the \&GDBG.Log
method.
Data Code Patterns
Field Tags
Getting Field Values in PPC
To get a FieldTag
(column segment field) value, use:
&G3FRM.record("RECORDTAG").field("FIELDNAME").Value
To get a RowsetFieldTag
(grid segment field) value, use:
&G3FRM.rowset("RECORDTAG").row(#).record("RECORDTAG").field("FIELDNAME").Value
Recommended for RecordTags
(column fields)
Required for RowsetTags
(grid fields)
FieldTag
Standard
&G3FRM.GetRecord("RECORDTAG").GetField("FIELDNAME").Value
Helium (Same code example from above)
&G3FRM.record("RECORDTAG").field("FIELDNAME").Value
RowsetFieldTag
Standard
&G3FRM.GetRowset("RECORDTAG").GetRow(#).GetField("FIELDNAME").Value
Helium (Same code example from above)
&G3FRM.rowset("RECORDTAG").row(#).record("RECORDTAG").field("FIELDNAME").Value
Tips to Find
Search and replace bold text from code examples
Setting Field Values in PPC
Use the FieldTag
’s value property to set a field value in PeopleCode. In Helium when a value is set, data and display effects are propagated through the Dependency Network. For example, suppose Field A has a FieldChange
method that sets the value of Field B. Normal propagation through the Dependency Network includes:
Data Propagation
- Data Pool keys that are satisfied by B and anything that depends on that Data Pool record
- SmartSources that are dependent on B
- Values of fields configured to be lifecycled from B ("Update when This Source Changes")
FieldChange
andFieldEdit
methods coded for field B
Display Propagation
- Painting fields whose value or description (related display) has changed
- Painting segments whose visible or editable state has changed
- Painting fields whose visible, editable, or required state has changed
- Painting custom segments (e.g.action items, attachments) that have VisualIfs that have changed
There may be instances when propagation should not occur. For example, if the FieldChange
method for Field A sets the value of Field B and the FieldChange
method for Field B sets the value of Field A, normal data propagation would result in an infinite loop. The GT Framework provides methods to control data and display propagation.
Helium Pattern | FieldChange Method | FieldEdit Method | Propagate Data | Propagate Display * |
---|---|---|---|---|
&fieldTag.Value = "test"; | Yes | Yes | Yes | Yes |
&fieldTag.setValueOnly("test"); | No | No | No | No |
&fieldTag.setValuePropagateData("test"); | Yes | Yes | Yes | No |
&fieldTag.setValuePropagateDisplay("test"); | No | No | No | Yes |
&fieldTag.SetValueSuppressChangePropagate("test"); | No | Yes | Yes | Yes |
&fieldTag.SetValueSuppressEditPropagate("test"); | Yes | No | Yes | Yes |
&fieldTag.SetValueSuppressEventsPropagate("test"); | No | No | Yes | Yes |
* Display won't fire if a form is instantiated and updated using PPC outside of the eForm component context.
Optional
In Standard, the FieldChange
and FieldEdit
methods could be suppressed. While the Standard methods will continue to work in Helium, it is recommended that the solution code be updated to use the new patterns. Search Standard code for "SuppressFieldChange
", "SuppressFieldEdit
" and "SetValueSuppressEvents
". Replace as shown.
SuppressFieldChange
Standard
&fieldTag.SuppressFieldChange = True; &fieldTag.Value = "test"; &fieldTag.SuppressFieldChange = False;
Helium
&fieldTag.SetValueSuppressChangePropagate("test");
SuppressFieldEdit
Standard
&fieldTag.SuppressFieldEdit = True; &fieldTag.Value = "test"; &fieldTag.SuppressFieldEdit = False;
Helium
&fieldTag.SetValueSuppressEditPropagate("test");
SetValueSuppressEvents
Standard
&fieldTag.SetValueSuppressEvents("test");
Helium
&fieldTag.SetValueSuppressEventsPropagate("test");
Set Initial Values to Version Zero of a Form Programmatically
The original version of a form refers to the values of Field Tags when a new form is created by the Add task. This includes the initialization values configured for segment fields. If a value is assigned programmatically, such as in FormInit
, this is considered a version change for the field. If form highlighting is enabled these fields will appear with a yellow background when the form opens.
Sometimes it is desired that a field set programmatically be considered the initial value of the field. Typically this is done in the FormInit
method of FormEvents
class. To set a field value and indicate that it is the original value use the FieldTag method setInitialValue
.
import G3FORM:Form;
class FormEvents
method FormInit();
end-class;
Component G3FORM:Form&G3FRM;
method FormInit
&G3FRM.record("PAGEREC").field("GSSETID").setInitialValue("SHARE");
&G3FRM.record("PAGEREC").field("GSSTATUS").setInitialValue("ACTIVE");
end-method;
In 3.50 Standard establishing the original value was accomplished by manipulating the underlying rowset properties of the form (&G3FRM
). With 3.50 Helium these rowset properties are no longer used to manage data.
Code Comparison
With 3.50 Standard copying mRowset
to mRowset_ORIG
will copy all field tags from the current data structure to the original.
The 3.50 Helium method is at the FieldTag
level so each field requiring initialization must be specified individually.
Standard
import G3FORM:Form;
class FormEvents method FormInit(); end-class;
Component G3FORM:Form&G3FRM;
method FormInit
&G3FRM.GetRecord("PAGEREC").GetField("GSSETID").Value = "SHARE"; &G3FRM.mRowset.CopyTo(&G3FRM.mRowset_ORIG);
end-method;
Helium
import G3FORM:Form;
class FormEvents method FormInit(); end-class;
Component G3FORM:Form&G3FRM;
method FormInit
&G3FRM.record("PAGEREC").field("GSSETID").setInitialValue("SHARE"); &G3FRM.record("PAGEREC").field("GSSTATUS").setInitialValue("ACTIVE");
end-method;
Non-Refactoring Impact
There will not be any form errors if the code is not updated. However, any programmatic assignments will not be considered the original version, which will show the field highlighted should this be enabled.
Tips to Find
Search solution PeopleCode for the usage of mRowset_ORIG
.
Get Rich Text HTML Value
Rich Text field data can either be stored as Json using the Delta JSON format or as HTML. The Delta JSON format is recommended. If there is a need to pull in an HTML value or save the Rich Text out as HTML, use the HTML option. To retrieve the HTML value, the getHTMLValue()
method should be used instead of the value property.
Standard
Local string &rtfValue = &G3FRM.GetRecord("PAGEREC").GetField("ADDRESSLONG").Value;
Helium
Local string &rtfValue = &G3FRM.record("PAGEREC").field("ADDRESSLONG").getHTMLvalue();
Tips to Find
Look through the segments in Form Setup to see if you are using rich text fields with the "Load/Save Rich Text as" option set to HTML and then search solution PeopleCode to see if there is code referencing that field.
Rowset Tags
Looping through Grid Rows
Always use the &rowsetTag.ActiveRowCount
property to loop through rows in a grid because it excludes deleted rows. Calling &rowsetTag.row(#)
as part of a paint hook that fires when a row is deleted will return the row that will be in that row position after the deleted row is actually deleted. If there is a need to access the deleted row, use rowInclDel(#)
instead to get the deleted row. To loop through rows including the deleted rows use &rowsetTag.RowCount
.
Accessing row 1 after deleting the last remaining row will return an empty row tag. The empty row tag is only returned to avoid errors that would result from returning a null. Changes to the returned row tag are ignored and will not end up in the row PeopleSoft defaults into the grid. This is because PeopleSoft's row delete event fires before the row is actually deleted and the default blank row is added back in.
Rowset Property
Required
&G3FRM.GetRowset("GRIDRECTAG").rowset
referred to the PeopleSoft rowset in &G3FRM.mRowset
that stores the GRIDRECTAG
grid data. mRowset
and the RowsetTag.rowset
property are no longer used in Helium. Some clients used this property to work around an issue with a FieldTag
or RecordTag
variables losing their connection to their row or &G3FRM.CurrentRowNumber
changing. Helium resolves this issue so the workaround of using the rowset property is not needed anymore and any code that references the rowset needs to be refactored to use the RowsetTag
instead.
Issue Example
This code is an example of the issue. In Standard, the &srcFieldTag
is disconnected from the row when the row is inserted and &tgtFieldTag
is set.
Standard - Issue
method GSMOVE_TO_BOTTOM_G_Change
Local G3FORM:TAGS:RowsetTag &rowsetTag = &G3FRM.GetRowset("GRIDTOP"); Local G3FORM:TAGS:FieldTag &srcFieldTag, &tgtFieldTag; &srcFieldTag = &rowsetTag.GetRow(&G3FRM.currentRowNumber).GetField("GSVALUE");
MessageBox(0, "", 0, 0, &srcFieldTag.Value); /shows a message box with a value of "A"/
&rowsetTag.InsertRow(&rowsetTag.ActiveRowCount); &tgtFieldTag = &rowsetTag.GetRow(&rowsetTag.ActiveRowCount).GetField("GSVALUE");
MessageBox(0, "", 0, 0, &srcFieldTag.Value); /shows a message box with a value of "" (blank)/
&tgtFieldTag.Value = &srcFieldTag.Value;
end-method;
Standard - Workaround
method GSMOVE_TO_BOTTOM_G_Change
Local Rowset &rowset = &G3FRM.GetRowset("GRIDTOP").rowset; Local Field &srcFld, &tgtFld; &srcFld = &rowset.GetRow(&G3FRM.currentRowNumber).GetRecord(Record.GSSEGMENTREC).GetField("GSVALUE");
MessageBox(0, "", 0, 0, &srcFld.Value); /shows a message box with a value of "A"/
&rowset.InsertRow(&rowset.ActiveRowCount); &tgtFld = &rowset.GetRow(&rowset.ActiveRowCount).GetField("GSVALUE");
MessageBox(0, "", 0, 0, & srcFld.Value); /shows a message box with a value of "A"/
&tgtFld.Value = &srcFld.Value;
end-method;
The issue code above that did not work in Standard works now in Helium. As explained in Current Row, the &_rowTag
should be added to the method declaration instead of using &G3FRM.currentRowNumber
.
Helium
method GSMOVE_TO_BOTTOM_G_Change(&_rowTag As G3FORM:TAGS:RowTag);
method GSMOVE_TO_BOTTOM_G_Change
Local G3FORM:TAGS:RowsetTag &rowsetTag = &G3FRM.rowset("GRIDTOP"); Local G3FORM:TAGS:FieldTag &srcFieldTag, &tgtFieldTag; &srcFieldTag = &_rowTag.record("GRIDTOP").field("GSVALUE");
MessageBox(0, "", 0, 0, &srcFieldTag.Value); /shows a message box with a value of "A"/
&rowsetTag.InsertRow(&rowsetTag.ActiveRowCount); &tgtFieldTag = &rowsetTag.row(&rowsetTag.ActiveRowCount).GetField("GSVALUE");
MessageBox(0, "", 0, 0, &srcFieldTag.Value); /shows a message box with a value of "A"/
&tgtFieldTag.Value = &srcFieldTag.Value;
end-method;
Tips to Find
Search for references to GetRowset("RECORDTAG").rowset
in the solution code
Getting Data Pool Record/Field Tag
Getting Data Pool Field Values
You can access a Data Pool record and field using this method:
&G3FRM.DPRowset("DPRECTAG").field("FIELDNAME").value
Recommended
The standard pattern is supported in Helium for backward compatibility
Standard
&G3FRM.PoolRecordList.Get("DPRECTAG").GetField("FIELDNAME").value
Helium
&G3FRM.DPRowset("DPRECTAG").field("FIELDNAME").value
Getting Segments
Getting Segment Drivers
You can access a Segment using this method:
&G3FRM.segment("SegmentName");
Recommended
Standard
&G3FRM.SegmentList.Get("SegmentName");
Helium
&G3FRM.segment("SegmentName");
Form mRowset
Required
mRowset
Deprecated
Form data in Standard at runtime in a PS rowset object and then serialized to an XML format. To make the data accessible in a form, the application class G3FORM:Form
has rowset properties mRowset
, mRowset_CURR
and mRowset_ORIG
. Form data in Helium is stored in JSON format and these properties no longer apply. Converting from the Standard pattern to the Helium pattern is an advanced topic. If a solution has code that references any of these properties, please contact support@gideontaylor.com for assistance in converting the code.
Tips to Find
Search solution code for:
.mRowset
.mRowset_CURR
.mRowset_ORIG
Data Code Patterns - Hooks
Grid Hooks
Getting the Current Row
When using one of these Hooks or Parts that needs to access the current row of a grid, add a new &_rowNum
integer parameter to the method declaration:
- For Hooks, use parameter
&_rowTag
AsG3FORM:TAGS:RowTag
FieldChange
FieldEdit
- For Parts, use parameter
&_rowNum
As Integer- SetSortDropDownList
- SmartSource
- Visual If Part
*Note: See the next section for parameter information for InsertRow
and DeleteRow
.
Using a &_rowNum
parameter on a SmartSource of Visualif is required. The Framework uses this as an indicator the Hook or Part is related to a grid.
method SMARTSRC_AmountByEmplType(&_rowNum As integer) Returns number;
/*@Dependency(Type="SmartSource", Value="[TEAMREC:GSEMP_TYPE]")*/
method SMARTSRC_AmountByEmplType
/+ &_rowNum as Integer +/
/+ Returns Number +/
Evaluate &G3FRM.rowset("TEAMREC").row(&_rowNum).record("TEAMREC").field("GSEMP_TYPE").Value
When = "E"
Return 500;
When = "M"
Return 1000;
When-Other
Return 0;
End-Evaluate;
end-method;
Required
Helium still supports &G3FRM.CurrentRowNumber
in some cases but it is deprecated. This is because it is not always accurate, for example when working with two grids simultaneously or having one row reference another row in the grid. The recommended approach is to refactor methods Helium offers with greater row control and dependability.
Standard
method SMARTSRC_AmountByEmplType() Returns number;
method SMARTSRC_AmountByEmplType /+ Returns Number +/
Evaluate &G3FRM.GetRowset("TEAMREC").GetRow(&G3FRM.CurrentRowNumber).GetField("GSEMP_TYPE").Value When = "E" Return 500; When = "M" Return 1000; When-Other Return 0; End-Evaluate;
end-method;
Helium (same code as example from above)
method SMARTSRC_AmountByEmplType(&_rowNum As integer) Returns number;
/@Dependency(Type="SmartSource", Value="[TEAMREC:GSEMP_TYPE]")/ method SMARTSRC_AmountByEmplType /+ &_rowNum as Integer +/ /+ Returns Number +/
Evaluate &G3FRM.rowset("TEAMREC").row(&_rowNum).record("TEAMREC").field("GSEMP_TYPE").Value When = "E" Return 500; When = "M" Return 1000; When-Other Return 0; End-Evaluate;
end-method;
Tips to Find
Look for references to &G3FRM.CurrentRowNumber
in solution PeopleCode. Change the Hook and Part methods that use &G3FRM.CurrentRowNumber
to take in the appropriate parameter (&_rowNum
as Integer or &_rowTag
As G3FORM:TAGS:RowTag
) and use that in place of G3FRM.CurrentRowNumber
.
InsertRow
, DeleteRow
InsertRow
and DeleteRow
hooks are used for responding to insert and deletions within a grid. Both methods take as parameters a RowTag
object which can be used to reference the current row information.
import G3FORM:TAGS:RowTag;
import G3FORM:TAGS:RecordTag;
class DATA_GRID
method DeleteRow(&_rowTag As G3FORM:TAGS:RowTag);
method InsertRow(&_rowTag As G3FORM:TAGS:RowTag);
end-class;
method DeleteRow
/+ &_rowTag as G3FORM:TAGS:RowTag +/
Local G3FORM:TAGS:RecordTag &recTag = &G3FRM.Record("PAGEREC");
Local number &rowAmount = &_rowTag.record("DATA_GRID").field("G_AMOUNT").Value;
&recTag.Field("G_TOTAL").value = &recTag.Field("G_TOTAL").value - &rowAmount;
end-method;
method InsertRow
/+ &_rowTag as G3FORM:TAGS:RowTag +/
&_rowTag.record("DATA_GRID").field("G_REQUIRED_FLG").Value = "Y";
&_rowTag.record("DATA_GRID").field("G_TOTAL_FLG").Value = "N";
end-method;
Required
Helium still supports &G3FRM.CurrentRowNumber
but it is deprecated. This is because it is not always accurate, for example when working with two grids simultaneously or having one row reference another row in the grid. The recommended approach is to refactor methods Helium offers with greater row control and dependability.
The two options are:
- Use new
RowsetEvent
ClassInsertRow
andDeleteRow
(Highly Recommended) - Update
InsertRowHook
to account for conceptual change with&G3FRM.CurrentRowNumber
Option 1 - Use the new RowsetEvent
class (Highly Recommended)
Type | Standard | Helium |
---|---|---|
Class | [Package]:[SegmentEvents]:SegmentName | [Package]:RowsetEvents:[Rowset Tag Name] |
Insert Method | InsertRowHook | InsertRow |
Delete Method | DeleteRowHook | DeleteRow |
Parameter | None | &_rowTag As G3FORM:TAGS:RowTag |
Helium recognizes a new RowsetEvents
Class and methods to Insert and Delete Rows that accept a RowTag
object parameter which can be used to reference data from the current row.
To use the 3.50 Helium methods, move the code from the Standard methods to the Helium methods.
Standard
import G3FORM:TAGS:RecordTag;
class MySegment method DeleteRowHook(); method InsertRowHook(); end-class;
method DeleteRowHook /+ &_rowTag as G3FORM:TAGS:RowTag +/
Local number &newRowNum = &G3FRM.currentRowNumber;
Local G3FORM:TAGS:RecordTag &recTag = &G3FRM.GetRecord("PAGEREC"); Local G3FORM:TAGS:RecordTag &GRID02; &GRID02 = &G3FRM.GetRowset("DATA_GRID").GetRow(&newRowNum); Local number &rowAmount = &GRID02.GetField("G_AMOUNT").Value; &recTag.GetField("G_TOTAL").value = &recTag.GetField("G_TOTAL").value - &rowAmount;
end-method;
method InsertRowHook
Local number &newRowNum = &G3FRM.currentRowNumber + 1;
Local G3FORM:TAGS:RecordTag &GRID02; &GRID02 = &G3FRM.GetRowset("DATA_GRID").GetRow(&newRowNum); &GRID02.GetField("G_REQUIRED_FLG").Value = "Y"; &GRID02.GetField("G_TOTAL_FLG").Value = "N";
end-method;
Helium
import G3FORM:TAGS:RowTag;
Import G3FORM:TAGS:RecordTag;
class DATA_GRID method DeleteRow(&_rowTag As G3FORM:TAGS:RowTag); method InsertRow(&_rowTag As G3FORM:TAGS:RowTag); end-class;
method DeleteRow /+ &_rowTag as G3FORM:TAGS:RowTag +/
Local G3FORM:TAGS:RecordTag &recTag = &G3FRM.Record("PAGEREC"); Local number &rowAmount = &_rowTag.record("DATA_GRID").field("G_AMOUNT").Value; &recTag.Field("G_TOTAL").value = &recTag.Field("G_TOTAL").value - &rowAmount;
end-method;
method InsertRow /+ &_rowTag as G3FORM:TAGS:RowTag +/
&_rowTag.record("DATA_GRID").field("G_REQUIRED_FLG").Value = "Y"; &_rowTag.record("DATA_GRID").field("G_TOTAL_FLG").Value = "N";
end-method;
Option 2 - Update InsertRowHook
Keep code in InsertRowHook
and InsertDeleteHook
and adjust usage of &G3FRM.CurrentRowNumber
. Helium still supports the CurrentRowNumber
on the G3FRM
object but it is deprecated. This is because it is not always accurate, for example when working with two grids simultaneously or having one row reference another row in the grid.
Note: &G3FRM.CurrentRowNumber
for deletions is identical between 3.50 Standard and Helium.
Standard
import G3FORM:TAGS:RecordTag;
class MySegment method InsertRowHook(); end-class;
method InsertRowHook
Local number &newRowNum = &G3FRM.currentRowNumber + 1;
Local G3FORM:TAGS:RecordTag &GRID02; &GRID02 = &G3FRM.GetRowset("DATA_GRID").GetRow(&newRowNum); &GRID02.GetField("G_REQUIRED_FLG").Value = "Y"; &GRID02.GetField("G_TOTAL_FLG").Value = "N";
end-method;
Helium
import G3FORM:TAGS:RecordTag;
class MySegment method InsertRowHook(); end-class;
method InsertRowHook
Local number &newRowNum = &G3FRM.currentRowNumber;
Local G3FORM:TAGS:RecordTag &GRID02; &GRID02 = &G3FRM.Rowset("DATA_GRID").Row(&newRowNum).GetRecord("DATA_GRID"); &GRID02.Field("G_REQUIRED_FLG").Value = "Y"; &GRID02.Field("G_TOTAL_FLG").Value = "N";
end-method;
Non-Refactoring impact
InsertRowHook
may cause errors if incrementing the &G3FRM.CurrentRowNumber
.
Tips to Find
Examine solution methods for use of all InsertRowHook
and/or &G3FRM.CurrentRowNumber
Validate Page Programmatically
The GT eForms framework verifies that required fields have values when the user attempts to navigate to the next page or when submitting the form. The framework has the means to check for required fields preemptively using the page (step) method CheckRequiredFields
.
For example, consider several fields are required for a web service call that is triggered by a form field button. Rather than checking each individual field’s value, the method CheckRequiredFields
may be used. This method has the same effect as the user clicking to the next page which verifies all required fields are entered.
method VALIDATE_BTN_Change
&G3FRM.StepStack.Stack [&G3FRM.StepStack.currentStep].CheckRequiredFields();
end-method;
Although the same method is used for Helium and Standard the internal implementation is significantly different.
Suppress Events when Field is Lifecycled
Suppose that Field A lifecycles Field B (e.g. Field B has "Update when This Source Changes" equal to Field A). In Standard, any FieldEdit
and FieldChange
events for Field B would not be triggered when Field A changes. In Helium, these events would be triggered. To make a Helium form have the Standard behavior, the "Disable Lifecycle Field Events" box needs to be checked on Form Setup and a Form Type Build needs to be completed.
Display Control Code Patterns
This section lists some of the display-related objects that can be accessed and manipulated. Patterns related to the aesthetic options – event hooks - are listed in the next section.
Field Display Control (GField
, GFieldGrid
)
Display fields are objects that are accessible with the framework API. The classes are known as GField
(column segment field) and GFieldGrid
(grid segment field). Both are accessed through the FieldTag
API. This section refers to GField
but is applicable equally to GFieldGrid
.
GField
is an object accessible through the FieldTag
using the GetGField
method:
Local G3SEG:GField &GF = &G3FRM.record("PAGEREC").field("MYFIELD").getGField("MySegment");
The getGField
method takes as input a segment name. This is because a single field tag can appear on multiple segments. By specifying the segment, the specific display of the field tag is referenced.
There are several methods and properties available for the GField
API:
Method or Property | Purpose |
---|---|
method setLabel(&_newLabel As string); | Set the label of the display field |
method isVisible() Returns boolean; method isRowVisible(&_rowNum As integer) Returns boolean; | Returns true or false the visibility of the field |
method isEditable() Returns boolean; method isRowDisplayOnly(&_rowNum As integer) Returns boolean; | Returns true or false the editability of the field |
method isRequired() Returns boolean; method isRowRequired(&_rowNum As integer) Returns boolean; | Returns true or false if the field is required or not |
method Repaint(); method RepaintRow(&_rowNum As integer); | Refresh the display of the field. This may be useful in a Segment Field Paint Hook because this hook executes after the standard field paint event. |
method setOverride(); | Gives the GField a component scope so changes made will persist between server trips. |
method getConfigField() returns G3FORM:TAGS:FieldTag; method getRowConfigField(&_rowNum As integer) Returns G3FORM:TAGS:FieldTag; | Returns the FieldTag for a GField/GFieldGrid object |
property Field OutputField; | Reference to component buffer record field associated with GField. |
property Field OutputFieldHTML; | Reference to component buffer record field associated with GField when configured as Rich Text/HTML output. |
In Standard the ConfigField
property for a GField
/GFieldGrid
referred its underlying FieldTag
. In Helium, that property is replaced by the method getConfigField()
.
Standard
Local G3SEG:GField &gfield = &G3FRM.SegmentList.Get("EmplInfo").FieldList.Get("EMPLID");
Local G3FORM:TAGS:FieldTag &fieldTag = &gfield.ConfigField;
Helium
Local G3SEG:GField = &G3FRM.Dep.getNode(&GCON.N_COLUMNSEGMENTFIELD, "EmplInfo$EMPLID") .getEntity();
Local G3FORM:TAGS:FieldTag &fieldTag = &gfield.getConfigField();
Non-Refactoring impact
Using ConfigField
on GFieldGrid
objects will fail or result in unexpected behavior. Using ConfigField
on GField
objects will likely continue to work but we recommend refactoring to use getConfigField
for consistency.
Tips to Find
Search solution code for ".ConfigField
".
Disabling Grid Insert Button
Disabling a Grid’s insert or delete buttons under certain circumstances requires a PeopleCode VisualIf being associated with either field on the configuration page. These are the built-in fields G3ROWINS
and G3ROWDEL
.
Consider, for example, a grid that should be limited to only five rows. The PeopleCode VisualIf will return true or false based on the number of rows in the grid.
In Helium, dependencies must be considered when creating custom methods that are data dependent. For this example, the dependency is with the RowsetTag
, which means that the VisualIf should be re-evaluated whenever it changes.
/*@Dependency(Type="RowsetTag", Value="GRID03")*/
method VISIF_HasLessThanFiveRows
/+ Returns Boolean +/
Local G3FORM:TAGS:RowsetTag &RowsetTag;
&RowsetTag = &G3FRM.rowset("GRID03");
Return (&RowsetTag.ActiveRowCount < 5);
end-method;
The code is nearly identical between Standard and Helium; the main difference is the annotation.
Standard
method VISIF_HasLessThanFiveRows /+ Returns Boolean +/ Local G3FORM:TAGS:Rowset &RowsetTag; &RowsetTag = &G3FRM.GetRowset("GRID03"); Return (&RowsetTag.ActiveRowCount < 5); end-method;
Helium
/*@Dependency(Type="RowsetTag", Value="GRID03")*/ method VISIF_HasLessThanFiveRows /+ Returns Boolean +/ Local G3FORM:TAGS:RowsetTag &RowsetTag; &RowsetTag = &G3FRM.rowset("GRID03"); Return (&RowsetTag.ActiveRowCount < 5); end-method;
Non-Refactoring impact
If the annotation is not added the VisualIf will not be fired as the user inserts and deletes rows.
Tips to Find
Search solution code for VisualIfs that depend on the number of rows in a grid.
Manipulating Drop-Downs
The SetSortDropDownList
hook can be used to change the drop-down items and how they are sorted in a drop-down prompt. These are available to both column and grid segment fields.
When a drop-down is configured, there are 4 sources for the drop-down items:
- AppPackage – This option just communicates to the functional users that the drop-down options are generated by code, specifically this
SetSortDropDownList
hook. - Long XLAT- The drop-down options come from the long descriptions in the translate values. The
SetSortDropDownList
hook can still be used with this option to add items, remove items, or sort items. - Prompt- The dropdown values come from a data pool record. The
SetSortDropDownList
hook can still be used with this option to add items, remove items, or sort items. - Short XLAT- The dropdown option come from the short descriptions in the translate values. The
SetSortDropDownList
hook can still be used with this option to add items, remove items, or sort items.
The method declaration is slightly different between drop-downs used on a column and for a grid:
Column Field
method SetSortDropDownList(&dd As G3LOOKUP_V2:Structure:DropDownOptionsList);
Grid Field
method SetSortDropDownList(&_rowNum As integer, &dd As G3LOOKUP_V2:Structure:DropDownOptionsList);
Helium – Example 1
This example shows a column-based drop-down populated programmatically, independent of the Data Pool.
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSSHOW_A_B_C]")*/
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSSHOW_X_Y_Z]")*/
method GSCOLUMN_SORTED_DR_SetSortDropDownList
/+ &dd as G3LOOKUP_V2:Structure:DropDownOptionsList +/
If &G3FRM.record("PAGEREC").field("GSSHOW_X_Y_Z").Value = "Y" Then
&dd.addOption("Z", "Z");
&dd.addOption("Y", "Y");
&dd.addOption("X", "X");
End-If;
If &G3FRM.record("PAGEREC").field("GSSHOW_A_B_C").Value = "Y" Then
&dd.addOption("C", "C");
&dd.addOption("B", "B");
&dd.addOption("A", "A");
End-If;
end-method;
Helium – Example 2
This example shows a column-based drop-down populated from the data pool. The SetSetDropDownList
"intercepts" the data before it is displayed. The example uses displayLIstInOrder
which un-sorts the drop-down. An additional option is added to the list.
method G3SOURCE_SetSortDropDownList
/+ &dd as G3LOOKUP_V2:Structure:DropDownOptionsList +/
&dd.displayListInOrder = True;
&dd.addOption("EXT", "External");
end-method
Required
Standard uses SortDropDown
. Helium replaces that hook with SetSortDropDownList
.
Standard
method GSCOLUMN_SORTED_DR_SortDropDown /+ &dd as G3LOOKUP:DropdownOptionsList +/
Local string &ABC = &G3FRM.GetRecord("PAGEREC").GetField("GSSHOW_A_B_C").Value; Local string &XYZ = &G3FRM.GetRecord("PAGEREC").GetField("GSSHOW_X_Y_Z").Value; &dd.displayListInOrder = True;
If (&XYZ = "Y") Then &dd.addOption("Z", "Z"); &dd.addOption("Y", "Y"); &dd.addOption("X", "X"); End-If;
If (&ABC = "Y") Then &dd.addOption("C", "C"); &dd.addOption("B", "B"); &dd.addOption("A", "A"); End-If;
end-method;
Helium
/@Dependency(Type="SmartSource", Value="[PAGEREC:GSSHOW_A_B_C]")/ /@Dependency(Type="SmartSource", Value="[PAGEREC:GSSHOW_X_Y_Z]")/ method GSCOLUMN_SORTED_DR_SetSortDropDownList /+ &dd as G3LOOKUP_V2:Structure:DropDownOptionsList +/
Local string &ABC = &G3FRM.record("PAGEREC").field("GSSHOW_A_B_C").Value; Local string &XYZ = &G3FRM.record("PAGEREC").field("GSSHOW_X_Y_Z").Value;
If (&XYZ = "Y") Then &dd.addOption("Z", "Z"); &dd.addOption("Y", "Y"); &dd.addOption("X", "X"); End-If;
If (&ABC = "Y") Then &dd.addOption("C", "C"); &dd.addOption("B", "B"); &dd.addOption("A", "A"); End-If;
end-method;
Non-Refactoring impact
Because Helium uses a new pattern the drop-down changes will silently not work.
Tips to Find
Search solution code for usage of Standard SortDropDown
method. This are found in the record tag classes under the FieldEvents
subpackage.
SetSortDropDownList
Grid
Required
Standard uses SortDropDown
. Helium replaces that hook with SetSortDropDownList
.
Standard
method GSGRID_SORTED_DROP_SortDropDown /+ &add as G3LOOKUP:DropdownOptionList +/ Local G3FORM:TAGS:RowsetTag &formRS = &G3FRM.GetRowset("DROPDOWNREC"); Local string &fieldGSSHOW_1_2_3, &fieldGSSHOW_4_5_6, &fieldGSSHOW_7_8_9;
&fieldGSSHOW_1_2_3 = &formsRS.GetRow(&G3FRM.currentRowNumber).GetField("GSSHOW_1_2_3").Value; &fieldGSSHOW_4_5_6 = &formsRS.GetRow(&G3FRM.currentRowNumber).GetField("GSSHOW_4_5_6").Value; &fieldGSSHOW_7_8_9 = &formsRS.GetRow(&G3FRM.currentRowNumber).GetField("GSSHOW_7_8_9").Value;
&dd.displayListInOrder = True; If &fieldGSSHOW_1_2_3 = "Y" Then &dd.addOption("1", "1"); &dd.addOption("2", "2"); &dd.addOption("3", "3"); End-If;
If &fieldGSSHOW_4_5_6 = "Y" Then &dd.addOption("4", "4"); &dd.addOption("5", "5"); &dd.addOption("6", "6"); End-If;
If &fieldGSSHOW_7_8_9 = "Y" Then &dd.addOption("7", "7"); &dd.addOption("8", "8"); &dd.addOption("9", "9"); End-If;
end-method;
Helium
/@Dependency(Type="SmartSource", Value="[DROPDOWNREC:GSSHOW_1_2_3]")/ /@Dependency(Type="SmartSource", Value="[DROPDOWNREC:GSSHOW_4_5_6]")/ /@Dependency(Type="SmartSource", Value="[DROPDOWNREC:GSSHOW_7_8_9]")/ method GSGRID_SORTED_DROP_SetSortDropDownList /+ &_rowNum as Integer +/ /+ &add as G3LOOKUP:DropdownOptionList +/ Local G3FORM:TAGS:Rowset &formRS = &G3FRM.rowset("DROPDOWNREC"); Local string &fieldGSSHOW_1_2_3, &fieldGSSHOW_4_5_6, &fieldGSSHOW_7_8_9;
&fieldGSSHOW_1_2_3 = &formsRS.row(&_rowNum).record("DROPDOWNREC").field("GSSHOW_1_2_3").Value; &fieldGSSHOW_4_5_6 = &formsRS.row(&_rowNum).record("DROPDOWNREC").field("GSSHOW_4_5_6").Value; &fieldGSSHOW_7_8_9 = &formsRS.row(&_rowNum).record("DROPDOWNREC").field("GSSHOW_7_8_9").Value;
&dd.displayListInOrder = True; If &fieldGSSHOW_1_2_3 = "Y" Then &dd.addOption("1", "1"); &dd.addOption("2", "2"); &dd.addOption("3", "3"); End-If;
If &fieldGSSHOW_4_5_6 = "Y" Then &dd.addOption("4", "4"); &dd.addOption("5", "5"); &dd.addOption("6", "6"); End-If;
If &fieldGSSHOW_7_8_9 = "Y" Then &dd.addOption("7", "7"); &dd.addOption("8", "8"); &dd.addOption("9", "9"); End-If;
end-method;
Tips to Find
Search solution code for usage of Standard SortDropDown
method. This are found in the record tag classes under the FieldEvents
subpackage.
Display Control Code Patterns - Hooks
The GT eForms Framework allows for several display-related hooks for controlling display. These occur in a specific sequence; developers should be conscious of this to achieve the intended behavior. Please refer to Appendix 1 for more information.
PostNav and Paint Order
Required
The PostNav
hook is designed to allow developers to change the display the first time a user navigates to a page. In Standard, there was a flaw with the PostNav
hook firing before Paint. This resulted in not being able to change the display in a PostNav
hook because the segments/fields would not be on the page yet. In Helium, this flaw has been resolved and PostNav
now fires after Paint. See Appendix 1: Event/Hook Flow for details of the order Framework events and hooks fire.
Standard | Helium |
---|---|
1) Solution Page PostNav | 1) GT Framework Page PostNav |
2) GT Framework Page PostNav | 2) GT Framework Page Paint |
3) Solution Page Paint | 3) Solution Page Paint |
4) GT Framework Page Paint | 4) Solution Page PostNav |
Standard
class PageEvents method PostNav(&_task As string, &_pageStepNbr As integer); end-class;
method PostNav /+ &_task as String, +/ /+ &_pageStepNbr as Integer +/ &G3FRM.GetRecord("PAGEREC").GetField("GSTEXTFIELD").Value = "test"; end-method;
Helium
If a solution contains a Page PostNav
event that affects a field that has already been painted, there are two options for getting the solution to work as desired. In these examples, GSTEXTFIELD
is on page 2 of the form and its value needs to be set before getting to the page.
Helium Option 1 - Trigger the Paint event again after the
PostNav
eventclass PageEvents method Paint$Default$ADD$2(); method PostNav$Default$ADD$2(); end-class;
method Paint$Default$ADD$2 end-method;
method PostNav$Default$ADD$3 &G3FRM.record("PAGEREC").field("GSTEXTFIELD").Value = "test"; %This.Paint$Default$ADD$2(); end-method;
Helium Option 2 - Trigger a
PreNav
event in a previous pageclass PageEvents method PreNav$Default$ADD$1(); end-class;
method PreNav$Default$ADD$1 &G3FRM.record("PAGEREC").field("GSTEXTFIELD").Value = "test"; end-method;
Tips to Find
Look in solution PeopleCode for any PostNav
hooks and refactor if code in those hooks changes form field values or sets properties it assumes will be painted after the hook fires.
Page Hooks
Page hooks encompass page-level display changes. These occur after segment and field-level display logic fires.
Page Paint
The Page Paint hook is used to change the display of a page as a user interacts with elements on a page. The Page Paint hook supports Annotations and refires when annotated dependencies change. See Annotations for more details. To create a Page Paint hook, create a method in the class [Form Application Package]:PageEvents following this convention:
Method Paint$[Condition]$[Task]$[Page Number]();
The following example of a Page Paint event hides the page title when a form checkbox field is checked.
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSHIDE_PAGE_LABEL]")*/
method Paint$Default$ADD$1
If &G3FRM.record("PAGEREC").field("GSHIDE_PAGE_LABEL").Value = "Y" Then
/*Hide the page label*/
GetLevel0()(1).G3FORM_WRK.G3STEP_TITLE.Visible = False;
Else
/*Show the page label*/
GetLevel0()(1).G3FORM_WRK.G3STEP_TITLE.Visible = True;
End-If;
end-method;
Required
Standard used a single page paint hook for all tasks and pages and logical branching in the method to fire logic for a specific task or page. Helium allows a specific Paint hook to be defined for each condition, task, and page that logic needs to fire. Standard Page Paint hooks must be refactored to use the new pattern to work on a Helium form.
Standard
method Paint /+ &_task as String, +/ /+ &_pageStepNbr as Integer +/
If &_pageStepNbr = 1 Then If (&G3FRM.GetRecord("PAGEREC").GetField("GSHIDE_PAGE_LABEL").Value = "Y") Then /Hide the page label/ GetLevel0()(1).G3FORM_WRK.G3STEP_TITLE.Visible = False; Else /Show the page label/ GetLevel0()(1).G3FORM_WRK.G3STEP_TITLE.Visible = True; End-If; End-If;
end-method;
Helium
/@Dependency(Type="SmartSource", Value="[PAGEREC:GSHIDE_PAGE_LABEL]")/ method Paint$Default$ADD$1
If &G3FRM.record("PAGEREC").field("GSHIDE_PAGE_LABEL").Value = "Y" Then /Hide the page label/ GetLevel0()(1).G3FORM_WRK.G3STEP_TITLE.Visible = False; Else /Show the page label/ GetLevel0()(1).G3FORM_WRK.G3STEP_TITLE.Visible = True; End-If;
end-method;
Tips to Find
Any Paint hooks in [Form Application Package]:PageEvents
need to be refactored.
Page PostNav
The Page PostNav
hook is designed to allow developers to change the display the first time a user navigates to a page. To create a Page PostNav
hook, create a method in the class [Form Application Package]:PageEvents
following this convention:
Method PostNav$[Condition]$[Task]$[Page Number]();
The following example of a Page PostNav
event pops up a message box once the user navigates to the second form page.
method PostNav$Default$ADD$2
MessageBox(0, "", 0, 0, "You are now entering the Second Page.");
end-method;
Required
Standard used a single page PostNav
hook for all tasks and pages and logical branching in the method to fire logic for a specific task or page. Helium allows a specific PostNav
hook to be defined for each condition, task, and page that logic needs to fire. Standard Page PostNav
hooks must be refactored to use the new pattern to work on a Helium form.
Standard
method PostNav /+ &_task as String, +/ /+ &pageStepNbr as Integer +/ If (&_pageStepNbr = 2) Then MessageBox(0, "", 0, 0, "You are now entering the Second Page."); End-If; end-method;
Helium
method PostNav$Default$ADD$2 MessageBox(0, "", 0, 0, "You are now entering the Second Page."); end-method;
Tips to Find
Any PostNav
hooks in [Form Application Package]:PageEvents
need to be refactored.
Page PreNav
The Page PreNav
hook is designed to fire logic when a user attempts to leave a page. This hook is typically used to fire validation logic that prevents a user from leaving a page if certain requirements are not met. (Note: Use field configuration to make a field required rather than code).
To create a Page PreNav
hook, create a method in the class [Form Application Package]:PageEvents
following this convention:
Method PreNav$[Condition]$[Task]$[Page Number]();
The following example of a Page PreNav
event prevents the user from submitting a form from the first page. (Note: Not an ideal form design)
method PreNav$Default$ADD$1
If (GetField().Name = "G3SUBMIT_PB") Then
Error ("This form cannot be submitted from the first page.");
End-If;
end-method;
Required
Standard used a single page PreNav
hook for all tasks and pages and logical branching in the method to fire logic for a specific task or page. Helium allows a specific PreNav
hook to be defined for each condition, task, and page that logic needs to fire. Standard Page PreNav
hooks must be refactored to use the new pattern to work on a Helium form.
Standard
method PreNav /+ &_task as String, +/ /+ &_pageStepNbr as Integer +/ If (&_pageStepNbr = 1 And &_task = "ADD" And GetField().Name = "G3SUBMIT_PB") Then Error ("This form cannot be submitted from the first page."); End-If; end-method;
Helium
method PreNav$Default$ADD$1 If (GetField().Name = "G3SUBMIT_PB") Then Error ("This form cannot be submitted from the first page."); End-If; end-method;
Tips to Find
Any PreNav hooks in [Form Application Package]:PageEvents
need to be refactored.
Segment Hooks
Segment hooks encompass segment-level display changes. These occur after field-level display logic fires and prior to page events.
Segment Paint
The Segment Paint hook is used to change the display of a segment as a user interacts with elements on a page. The Segment Paint hook supports Annotations and refires when annotated dependencies change. See Annotations for more details. To create a Segment Paint hook, create a method in the class [Form Application Package]:SegmentEvents:[SegmentName]
following this convention:
Method PaintHook();
The following example of a Segment Paint event hides the segment instructions when a form checkbox field is checked.
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSHIDE_SEGMENT_INS]")*/
method Paint
If &G3FRM.record("PAGEREC").field("GSHIDE_SEGMENT_INS").Value = "Y" Then
/*Hide the segment instructions*/
&G3FRM.segment("INITIALCHECKBOXES").OutputRow.G3DEV_FLD_DRV.G3SEG_INSTR.Visible = False;
Else
/*Show the segment instructions*/
&G3FRM.segment("INITIALCHECKBOXES").OutputRow.G3DEV_FLD_DRV.G3SEG_INSTR.Visible = True;
End-If;
end-method;
Required
The segment paint method name changed from PaintHook
in Standard to Paint in Helium.
Standard
method PaintHook If &G3FRM.GetRecord("PAGEREC").GetField("GSHIDE_SEGMENT_INS").Value = "Y" Then /Hide the segment instructions/
&G3FRM.SegmentList.get("INITIALCHECKBOXES").OutputRow.G3DEV_FLD_DRV.G3SEG_INSTR.Visible = False; Else /Show the segment instructions/
&G3FRM.SegmentList.get("INITIALCHECKBOXES").OutputRow.G3DEV_FLD_DRV.G3SEG_INSTR.Visible = True; End-If; end-method;
Helium
/@Dependency(Type="SmartSource", Value="[PAGEREC:GSHIDE_SEGMENT_INS]")/ method Paint If &G3FRM.record("PAGEREC").field("GSHIDE_SEGMENT_INS").Value = "Y" Then /Hide the segment instructions/ &G3FRM.segment("INITIALCHECKBOXES").OutputRow.G3DEV_FLD_DRV.G3SEG_INSTR.Visible = False; Else /Show the segment instructions/ &G3FRM.segment("INITIALCHECKBOXES").OutputRow.G3DEV_FLD_DRV.G3SEG_INSTR.Visible = True; End-If; end-method;
Tips to Find
Any PaintHook
methods in [Form Application Package]:SegmentEvents:[SegmentName]
need to be refactored to be Paint methods.
Segment PostNav
The Segment PostNav
hook is designed to allow developers to change the display of a segment the first time a user navigates to a page with the segment on it. To create a Segment PostNav
hook, create a method in the class [Form Application Package]:SegmentEvents:[SegmentName]
following this convention:
Method PostNav();
Required
The segment PostNav
hook method name changed from PostNavHook
in Standard to PostNav
in Helium.
Tips to Find
Any PostNavHook
methods in [Form Application Package]:SegmentEvents:[SegmentName]
need to be refactored to be PostNav
methods.
Segment PreNav
The Segment PreNav
hook is designed to fire logic when a user attempts to leave a page with that segment on it. This hook is typically used to fire validation logic that prevents a user from leaving a page with the segment if certain requirements are not met. (Note: Use field configuration to make a field required rather than code). To create a Segment PreNav
hook, create a method in the class [Form Application Package]:SegmentEvents:[SegmentName]
following this convention:
Method PreNav();
Required
The segment PreNav
hook method name changed from PreNavHook
in Standard to PreNav
in Helium.
Tips to Find
Any PreNavHook
methods in [Form Application Package]:SegmentEvents:[SegmentName]
need to be refactored to be PreNav
methods.
Field Events
Segment Field Paint
Use the Segment Field Paint hook to change the way a field is displayed on the form (e.g., Highlighting the field, adding a border to draw attention). Do not use the FieldChange
hook as it fires before the framework paints a field and any styling set will likely be overridden. This is the first display event hook to fire, after the framework paints a segment field and before any segment-related display hooks. See Appendix 1: Event/Hook Flow for the order Framework events and hooks fire.
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSEND_DATE]")*/
method PaintField$GSSTART_DATE
Local G3FORM:TAGS:FieldTag &startDtField;
&startDtField = &G3FRM.record("PAGEREC").field("GSSTART_DATE");
Local date &startDt = &startDtField.Value;
Local date &endDt = &G3FRM.record("PAGEREC").field("GSEND_DATE").Value;
Local Field &outputField = &startDtField.getGField("DATEHIGHLIGHTER").OutputField;
If All(&endDt) And
None(&startDt) Then
Local string &style = " style='border-color:blue; border-width:thick;'";
&outputField.HtmlAttributes = &outputField.HtmlAttributes | &style;
Else
&outputField.HtmlAttributes = "";
End-If;
end-method;
Required
How it was changed:
This moves the code that is related to field display into a class that handles field display on segments. Code that handles the display for a field on a given segment should be handled in the code for that segment. In the examples above, part of the code is moved from the FieldChange
method into a Paint method for the GSSTART_DATE
field on the DATEHIGHLIGHTER
segment, and a dependency is added (using a @Dependency annotation). The pieces of code that are related to data remain in the FieldChange
method, and the pieces related to display are extracted. Another important change is changing how the field values are accessed from &G3FRM.GetRecord().GetField().Value
to &G3FRM.record().field().Value
. This allows dependencies to be built properly between events and fields/SmartSources.
Standard
G_FORM_REFACTOR:FieldEvents:PAGEREC
method GSEND_DATE_Change
Local G3FORM:TAGS:FieldTag &startDtField = &G3FRM.GetRecord("PAGEREC").GetField("GSSTART_DATE"); Local Date &startDate = &startDtField.Value; Local Date &endDate = &G3FRM.GetRecord("PAGEREC").GetField("GSEND_DATE").Value;
Local Field &outputField = &startDtField.getGField("DATEHIGHLIGHTER").OutputField;
If None(&startDate) And All(&endDate) Then &outputField.HtmlAttributes = &outputField.HtmlAttributes | " style='border-color:blue; border-width:thick; background-color:#FFFF80;'"; MessageBox(0, "", 0, 0, "You must enter a Start Date before you can enter an End Date");
Else
&outputField.HtmlAttributes = ""; End-If;
end-method;
Helium
G_FORM_REFACTOR:FieldEvents:PAGEREC
method GSEND_DATE_Change
Local Date &startDate = &G3FRM.record("PAGEREC").field("GSSTART_DATE").Value; Local Date &endDate = &G3FRM.record("PAGEREC").field("GSEND_DATE").Value;
If None(&startDate) And All(&endDate) Then
MessageBox(0, "", 0, 0, "You must enter a Start Date before you can enter an End Date");
End-If;
end-method;
G_FORM_REFACTOR:SegmentEvents:DATEHIGHLIGHTER
/@Dependency(Type="SmartSource", Value="[PAGEREC:GSEND_DATE]")/ method PaintField$GSSTART_DATE
Local G3FORM:TAGS:FieldTag &startDtField = &G3FRM.record("PAGEREC").field("GSSTART_DATE"); Local date &startDt = &startDtField.Value; Local date &endDt = &G3FRM.record("PAGEREC").field("GSEND_DATE").Value; Local Field &outputField = &startDtField.getGField("DATEHIGHLIGHTER").OutputField;
If All(&endDt) And None(&startDt) Then
&outputField.HtmlAttributes = &outputField.HtmlAttributes | " style='border-color:blue; border-width:thick; background-color:#FFFF80;'";
Else
&outputField.HtmlAttributes = ""; End-If;
end-method;
Tips to Find
If code is changing the display of GFields
/OutputFields
inside a method in a FieldEvents
class, then it should be moved into a Paint method for the field inside of the segment class. Searching for "getGField
" may find examples of code that needs to be refactored.
Parts
PPC Visual If Parts
Annotations
If a PPC Visual If Part needs to return a Boolean based on the value of a form field, an annotation should be used. In this example, the Visual If Part returns true if the first letter of a column segment field is capitalized:
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSNOUN]")*/
method VISIF_NounCheckbox
/+ Returns Boolean +/
Local string &input;
Local string &firstLetter;
&input = &G3FRM.record("PAGEREC").field("GSNOUN").Value;
&firstLetter = Substring(&input, 1, 1);
If &firstLetter = Upper(&firstLetter) Then
Return True;
End-If;
Return False;
end-method;
Required
In Standard, all the page, segment, and field Visual Ifs are resolved as a user makes changes on a form. In Helium, as the user makes changes on a form, the only Visual Ifs that resolve are those that are affected by the change and relate to the current page display.
Standard
method VISIF_NounCheckbox /+ Returns Boolean +/
Local string &input; Local string &firstLetter;
&input = &G3FRM.Get("PAGEREC.GSNOUN"); &firstLetter = Substring(&input, 1, 1); If &firstLetter = Upper(&firstLetter) Then Return False; End-If;
Return True;
end-method;
Helium
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSNOUN]")*/ method VISIF_NounCheckbox /+ Returns Boolean +/
Local string &input; Local string &firstLetter;
&input = &G3FRM.record("PAGEREC").field("GSNOUN").Value; &firstLetter = Substring(&input, 1, 1); If &firstLetter = Upper(&firstLetter) Then Return False; End-If;
Return True;
end-method;
Tips to Find
All PPC Visual If Parts that depend on fields (SmartSources) or Visual Ifs on the page where the PPC Visual If Part is used need to be refactored to use annotations.
Grid Row Field & Annotation
If a PPC Visual If is used in a grid context and needs to access field data from the current row, its method should take in a &_rowNum
integer parameter. See Current Row. In this example, the Visual If Part returns true if the first letter of a grid segment field is capitalized:
/*@Dependency(Type="SmartSource", Value="[VERBCAPTAG:GSVERB]")*/
method VISIF_VerbCheckbox
/+ &_rowNum as Integer +/
/+ Returns Boolean +/
Local G3FORM:TAGS:RowsetTag &gridRowset;
Local string &inputVerb;
&gridRowset = &G3FRM.rowset("VERBCAPTAG");
&inputVerb = &gridRowset.row(&_rowNum).record("VERBCAPTAG").field("GSVERB").Value;
If &inputVerb = Upper(&inputVerb) Then
Return True;
End-If;
Return False;
end-method;
Recommended - &_rowNum
Required - Annotations
&G3FRM.currentRowNumber
is supported in Helium but it is highly recommended to use &_rowNum
instead. (See Current Row)
Standard
method VISIF_VerbCheckboxHydrogen /+ Returns Boolean +/ Local G3FORM:TAGS:Rowset &gridRowset = &G3FRM.GetRowset("VERBCAPTAG"); Local string &inputVerb;
&inputVerb = &gridRowset.GetRow(&G3FRM.currentRowNumber).GetField("GSVERB").Value; If &inputVerb = Upper(&inputVerb) Then Return True; End-If;
Return False;
end-method;
Helium
/*@Dependency(Type="SmartSource", Value="[VERBCAPTAG:GSVERB]")*/ method VISIF_VerbCheckbox /+ &_rowNum as Integer +/ /+ Returns Boolean +/ Local G3FORM:TAGS:RowsetTag &gridRowset = &G3FRM.rowset("VERBCAPTAG"); Local string &inputVerb;
&inputVerb = &gridRowset.row(&_rowNum).record("VERBCAPTAG").field("GSVERB").Value; If &inputVerb = Upper(&inputVerb) Then Return True; End-If;
Return False;
end-method;
Tips to Find
All PPC Visual If Parts that depend on fields (SmartSources) or Visual Ifs on the page where the PPC Visual If Part is used need to be refactored to use annotations.
Annotations - Dependency on a Visual If
Column Segment
Suppose a form has segment COLUMNSEG
with fields GSFIELDA
and GSFIELDB
. GSFIELDA
has a Show condition Visual If with an ID of 2d06de64-4d71-11ec-b64e-4d31b27488e8
(as found in the Network Visualizer). GSFIELDB
is only required when GSFIELDA
is visible. GSFIELDB
has a Required condition Visual If of "Is Field A Visible".
method VISIF_IsFieldAVisible() Returns boolean;
/*@Dependency(Type="VisualIf", Value="2d06de64-4d71-11ec-b64e-4d31b27488e8")*/
method VISIF_IsFieldAVisible
/+ Returns Boolean +/
Return &G3FRM.VISIF.Run("2d06de64-4d71-11ec-b64e-4d31b27488e8");
end-method;
Recommended
Standard
method VISIF_IsFieldAVisible() Returns boolean;
method VISIF_IsFieldAVisible /+ Returns Boolean +/
Return &G3FRM.GetRecord("PAGEREC").GetField("GSFIELDA").getGField("COLUMNSEG").Visible;
end-method;
Helium
method VISIF_IsFieldAVisible() Returns boolean;
/@Dependency(Type="VisualIf", Value="2d06de64-4d71-11ec-b64e-4d31b27488e8")/ method VISIF_IsFieldAVisible /+ Returns Boolean +/
Return &G3FRM.VISIF.Run("2d06de64-4d71-11ec-b64e-4d31b27488e8");
end-method;
Grid Segment
Suppose a form has segment GRIDSEG
with a record tag of GRID01
and fields GSFIELDC
and GSFIELDF
. GSFIELDC
has a Show condition Visual If with an ID of 2d06de64-4d71-11ec-b64e-4d31b27488e8
(as found in the Network Visualizer). GSFIELDF
is only required when GSFIELDC
is visible. GSFIELDF
has a Required condition Visual If of "Is Field C Visible".
method VISIF_IsFieldCVisible(&_rowNum As integer) Returns boolean;
/*@Dependency(Type="VisualIf", Value="2d06de64-4d71-11ec-b64e-4d31b27488e8")*/
method VISIF_IsFieldCVisible
/+ &_rowNum as Integer +/
/+ Returns Boolean +/
Return &G3FRM.VISIF.rowRun("2d06de64-4d71-11ec-b64e-4d31b27488e8", &_rowNum);
end-method;
Recommended
Standard
method VISIF_IsFieldCVisible() Returns boolean;
method VISIF_IsFieldCVisible /+ Returns Boolean +/
Return &G3FRM.GetRowset("GRID01").GetRow(&G3FRM.currentRowNumber).GetField("GSFIELDC").getGField("GRIDSEG").Visible;end-method;
Helium
method VISIF_IsFieldCVisible(&_rowNum As integer) Returns boolean;
/@Dependency(Type="VisualIf", Value="2d06de64-4d71-11ec-b64e-4d31b27488e8")/ method VISIF_IsFieldCVisible /+ &_rowNum as Integer +/ /+ Returns Boolean +/
Return &G3FRM.VISIF.rowRun("2d06de64-4d71-11ec-b64e-4d31b27488e8", &_rowNum);
end-method;
PPC SmartSources
Annotations
A PeopleCode SmartSource that depends on form data needs to be coded in a way that the Framework can detect the dependency and know it needs to update the SmartSource when that form data changes.
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSEXPENSES]")*/
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSREVENUE]")*/
method SMARTSRC_Profit
/+ Returns Number +/
Local number &expensesValue;
Local number &revenueValue;
&expensesValue = &G3FRM.record("PAGEREC").field("GSEXPENSES").Value;
&revenueValue = &G3FRM.record("PAGEREC").field("GSREVENUE").Value;
Return (&revenueValue - &expensesValue);
end-method;
Required
In Standard, dependencies were detected by use of the &G3FRM.Get()
method. In Helium, the dependency is detected using annotations.
&G3FRM.Get()
method is used to subscribe to the GSEXPENSES
and GSREVENUE
fields. If those fields change, this SmartSource gets recalculated. We changed the Get method calls to &G3FRM.record().field()
calls and added annotations for the GSEXPENSES
and GSREVENUE
fields. If those fields change, this SmartSource gets recalculated.
Note: The Get()
method is supported in Helium for backward compatibility but we highly recommend using annotations because they are more readable and intuitive.
Standard
method SMARTSRC_Profit /+ Returns Number +/
Local number &expensesValue; Local number &revenueValue;
&expensesValue = &G3FRM.Get("PAGEREC.GSEXPENSES"); &revenueValue = &G3FRM.Get("PAGEREC.GSREVENUE");
Return (&revenueValue - &expensesValue);
end-method;
Helium
/@Dependency(Type="SmartSource", Value="[PAGEREC:GSEXPENSES]")/ /@Dependency(Type="SmartSource", Value="[PAGEREC:GSREVENUE]")/ method SMARTSRC_Profit /+ Returns Number +/
Local number &expensesValue; Local number &revenueValue;
&expensesValue = &G3FRM.record("PAGEREC").field("GSEXPENSES").Value; &revenueValue = &G3FRM.record("PAGEREC").field("GSREVENUE").Value;
Return (&revenueValue - &expensesValue);
end-method;
Tips to Find
Search solution PPC for references to &G3FRM.Get()
and change it to use annotations and the Helium field access methods (e.g. &G3FRM.record().field()
)
Grid Row Field & Annotation
If a PPC SmartSource is used in a grid context and needs to access field data from the current row, its method should take in a &_rowNum
integer parameter. See Current Row. In this example, the PPC SmartSource returns the total of the bonus and earnings fields on the current row:
/*@Dependency(Type="SmartSource", Value="[BONUSCALCTAG:GSEARNINGS]")*/
/*@Dependency(Type="SmartSource", Value="[BONUSCALCTAG:GSBONUS]")*/
method SMARTSRC_TotalEarningsEJ
/+ &_rowNum as Integer +/
/+ Returns Number +/
Local number &earningsValue;
Local number &bonusValue;
Local G3FORM:TAGS:RowsetTag &gridRowsetHelium = &G3FRM.rowset("BONUSCALCTAG");
&earningsValue = &gridRowsetHelium.row(&_rowNum).record("BONUSCALCTAG").field("GSEARNINGS").Value;
&bonusValue = &gridRowsetHelium.row(&_rowNum).record("BONUSCALCTAG").field("GSBONUS").Value;
Return (&earningsValue + &bonusValue);
end-method;
Recommended
Using PPC SmartSource that subscribes to grid fields was not possible in Standard so FieldChange
events had to be used. In Helium, PPC SmartSources can subscribe to grid fields using Annotations.
Standard
method GSEARNINGS_Change Local G3FORM:TAGS:Rowset &gridRows = &G3FRM.GetRowset("BONUSCALCTAG"); Local number &bonusValue; Local number &earningsValue; Local number &totalValue;
&bonusValue = &gridRows.GetRow(&G3FRM.currentRowNumber).GetField("GSBONUS").Value; &earningsValue = &gridRows.GetRow(&G3FRM.currentRowNumber).GetField("GSEARNINGS").Value; &totalValue = &bonusValue + &earningsValue;
&gridRows.GetRow(&G3FRM.currentRowNumber).GetField("GSTOTAL_EARNINGS_W").Value = &totalValue;
end-method;
method GSBONUS_Change Local G3FORM:TAGS:Rowset &gridRows = &G3FRM.GetRowset("BONUSCALCTAG"); Local number &bonusValue; Local number &earningsValue; Local number &totalValue;
&bonusValue = &gridRows.GetRow(&G3FRM.currentRowNumber).GetField("GSBONUS").Value; &earningsValue = &gridRows.GetRow(&G3FRM.currentRowNumber).GetField("GSEARNINGS").Value; &totalValue = &bonusValue + &earningsValue;
&gridRows.GetRow(&G3FRM.currentRowNumber).GetField("GSTOTAL_EARNINGS_W").Value = &totalValue;
end-method;
Helium
/*@Dependency(Type="SmartSource", Value="[BONUSCALCTAG:GSEARNINGS]")*/ /*@Dependency(Type="SmartSource", Value="[BONUSCALCTAG:GSBONUS]")*/ method SMARTSRC_TotalEarningsEJ /+ &_rowNum as Integer +/ /+ Returns Number +/
Local number &earningsValue; Local number &bonusValue;
Local G3FORM:TAGS:RowsetTag &gridRowsetHelium = &G3FRM.rowset("BONUSCALCTAG");
&earningsValue = &gridRowsetHelium.row(&_rowNum).record("BONUSCALCTAG").field("GSEARNINGS").Value; &bonusValue = &gridRowsetHelium.row(&_rowNum).record("BONUSCALCTAG").field("GSBONUS").Value;
Return (&earningsValue + &bonusValue);
end-method;
Tips to Find
FieldChange
events on grid fields that set other grid fields could be refactored to use a subscription pattern using annotated PPC SmartSources.
Annotations – Dependency on a Visual If
Suppose a form has a checkbox field (GSCHECKBOX
) and two numeric fields (GSFIELDA
and GSFIELDB
). A Visual If is created such that if GSCHECKBOX
is checked, then the Visual If resolves to true; otherwise it resolves to false. When GSCHECKBOX
is checked, then GSFIELDB
= GSFIELDA
multiplied by 10. When not checked, then GSFIELDB
= GSFIELDA
divided by 10.
In Helium, this would be accomplished by having a SmartSource "FieldAMultiplier
" that lifecycles ("Update when This Source Changes") GSFIELDB
. de174db8-c244-11ec-9f7d-ad4d45dc2b52
is the Visual If ID (as found in the Network Visualizer)
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSFIELDA]")*/
/*@Dependency(Type="VisualIf", Value="de174db8-c244-11ec-9f7d-ad4d45dc2b52")*/
method SMARTSRC_FieldAMultiplier
/+ Returns Number +/
If &G3FRM.VISIF.run("de174db8-c244-11ec-9f7d-ad4d45dc2b52") Then
Return (&G3FRM.record("PAGEREC").field("GSFIELDA").Value * 10);
Else
Return (&G3FRM.record("PAGEREC").field("GSFIELDA").Value / 10);
End-If;
end-method;
Recommended
In Standard, this would be accomplished through FieldChange
methods on all the fields that could affect the value of GSFIELDB
. In Helium, this would be accomplished by having a SmartSource "FieldAMultiplier
" that lifecycles ("Update when This Source Changes") GSFIELDB
.
Standard
method CALC_FIELDB
Local number &numA = &G3FRM.GetRecord("PAGEREC").GetField("GSFIELDA").Value; Local number &numB = 0; Local string &chckBx = String(&G3FRM.Get("PAGEREC.GSCHECKBOX"));
If (&chckBx = "Y") Then &numB = &numA * 10; Else &numB = &numA / 10; End-If;
&G3FRM.GetRecord("PAGEREC").GetField("GSFIELDB").Value = &numB;
end-method;
method GSFIELDA_CHANGE%This.CALC_FIELDB();
end-method;
method GSCHECKBOX_CHANGE
%This.CALC_FIELDB();
end-method;
Helium
/*@Dependency(Type="SmartSource", Value="[PAGEREC:GSFIELDA]")*/ /*@Dependency(Type="VisualIf", Value="de174db8-c244-11ec-9f7d-ad4d45dc2b52")*/ method SMARTSRC_FieldAMultiplier /+ Returns Number +/
If &G3FRM.VISIF.run("de174db8-c244-11ec-9f7d-ad4d45dc2b52") Then Return (&G3FRM.record("PAGEREC").field("GSFIELDA").Value * 10); Else Return (&G3FRM.record("PAGEREC").field("GSFIELDA").Value / 10); End-If;
end-method;
Using the SmartSource and Visual If Factories
Solution code can use the SmartSource Factory to get the value of a SmartSource. The same can be done with VisualIfs.
The proper means for calling these factories is to use the properties associated with &G3FRM
.
- SmartSources – access using
&G3FRM.SSF
- VisualIf – access using
&G3FRM.VISIF
To run or execute the part, use the run method.
Local G3LOGIC:SmartSource &smartSource = &G3FRM.SSF.GetById("View Task U R L");
Local string &viewLink = &smartSource.run();
Local G3LOGIC:VisualIf &visif = &G3FRM.VISIF.GetById("Test Mode User");
Local boolean &isTestModeUser = &visif.run();
Required
In Helium the SmartSource and VisualIf factory cannot be instantiated; getting these parts must come from the form object as identified above. This is because the factories reference the dependency network that is managed by the form.
Standard also uses the Resolve
method to resolve the part, while Helium uses the run
method.
Standard
Local G3LOGIC:SmartSourceFactory &SSFAC = create G3LOGIC:SmartSourceFactory( Null, Null); Local G3LOGIC:SmartSource &smartSource = &SSFAC.GetById("View Task U R L"); Local string &viewLink = &smartSource.Resolve();
Local G3LOGIC:VisualIfFactory &VISFAC = create G3LOGIC:VisualIfFactory( Null); Local G3LOGIC:VisualIf &visif = &VISFAC.GetById("Test Mode User"); LLocal boolean &isTestModeUser = &visif.Resolve();
Helium
Local G3LOGIC:SmartSource &smartSource = &G3FRM.SSF.GetById("View Task U R L"); Local string &viewLink = &smartSource.run();
Local G3LOGIC:VisualIf &visif = &G3FRM.VISIF.GetById("Test Mode User"); Local boolean &isTestModeUser = &visif.run();
Tips to Find
Search custom code for local declaration of the custom part application class. Also search for the Resolve
method as custom code could be calling this method directly from one of the form’s properties (SSF or VISIF).
Grid Prepopulation
Grids can be programmatically loaded with data in the ADD task when the form opens. The following steps are taken to accomplish this:
- Create Population method in the
LogicParts
class of the form’s application package. - Run the Form Build process or the advanced steps for updating the registry and creating custom parts.
- Create or associate data pool record with custom load program. Note there are naming conventions for properly corresponding a data pool record with a custom load.
- On the Form Field Links of the data pool configuration:
- Map the
RowsetTag
to the DataPool record in the Initialize column. - Map the Form fields to the DataPool fields
- Map the
- Run Form Build process to pick up new DataPool configuration so it is included in the dependency network.
More comprehensive instructions may be found in Working With Grid Segments.
Associate the Data Pool Record with a Custom Load:
Map the Form fields to initialize from the Data Pool fields:
DATAPOOL_1_$<*PS Record*>$_*<descriptive name*>(&_rec As Record) Returns Rowset;
class LogicParts
method DATAPOOL_1_$DEPENDENT_VW$_EmployeeDependents(&_rec As Record) Returns Rowset;
end-class;
method DATAPOOL_1_$DEPENDENT_VW$_EmployeeDependents
/+ &_rec as Record +/
/+ Returns Rowset +/
Local Rowset &rs = CreateRowset(Record.DEPENDENT_VW);
Local string &emplid = &_rec.EMPLID.Value;
&rs.Fill("WHERE EMPLID = :1", &emplid);
Return &rs;
end-method;
No refactoring is required for Helium.
Efficient Propagation of Grid Data With PPC
When developing solution code to programmatically load a grid, a new pattern can be used to avoid unnecessary framework events. This is particularly beneficial when the grid is associated with a rowset-based smart source. Rowset-based smart sources cause a lot of processing because adding a row, deleting a row, and sometimes setting a value on single grid field causes the entire grid to be reevaluated. When the new pattern is used, some events are postponed until the developer is done loading the grid, by applying the bulkResolveportion
of the pattern."
Before flushing a rowsetTag
or loading a rowsetTag
, set the new property bulkLoad
to true. View sample code below:
method DEPTID_Change
Local string &deptid = &G3FRM.record("PAGEREC").field("DEPTID").Value;
Local G3FORM:TAGS:RowsetTag &rowsetTag = &G3FRM.rowset("GRID01");
&rowsetTag.bulkLoad = True; /* new */
&rowsetTag.flush();
After loading the rowsetTag
data, call the new method bulkResolve
.
For example:
Local Rowset &rsJobDeptVw = CreateRowset(Record.G_JOB_DEPT_VW);
Local integer &rowsFound = &rsJobDeptVw.Fill("WHERE DEPTID = :1", &deptid);
Local integer &i;
For &i = 1 To &rsJobDeptVw.ActiveRowCount;
If &i <> 1 Then
&rowsetTag.insertRow(&rowsetTag.ActiveRowCount);
End-If;
/* load data into each row of the rowsetTag */
End-For;
&rowsetTag.bulkResolve(); /* new */
Custom Components and Custom Segments
A custom PeopleSoft component and page can be used as a form page. This is typically done when a more complex data structure is needed, for example more levels of data than a level 1 grid.
The steps required for a custom component/page are:
- Create a PeopleSoft subpage with the custom records, fields, etc.
- Clone PeopleSoft page
G3FORM_PAGE_FL
(and PeopleCode) - Clone PeopleSoft component
G3FORM_PAGE_FL
(and PeopleCode) - Put custom component into a menu
- Place created subpage in step one into page in step two.
- Place cloned page in step two into cloned component in step three.
- Create class in the Form’s application package. The class has these requirements:
- Must extend
G3SEG:CustomSegment
- Constructor must instantiate the super class
- Have a method,
GetSubpage
, which returns the name of the subpage in step one. - Have a getter string property, name, which returns the name of the segment; this can be any descriptive value
- [Optional] Associate the data structure (rowset) in the subpage to the form’s data structure using the method
JointToForm_HE
. This is required to both save and reload custom data.
- Must extend
- Register custom segment override by running Form Build.
- Identify in Form Setup the custom segment override on the Data page
- Identify the custom component and page in the Form Setup for the desired condition and action
import G3SEG:CustomSegment;
import G3FORM:Form;
class MyCustomSegment extends G3SEG:CustomSegment
method MyCustomSegment(&_form As G3FORM:Form);
method GetSubpage() Returns string;
method JoinToForm_HE();
property string name get;
end-class;
Component G3FORM:Form&G3FRM;
method MyCustomSegment
/+ &_form as G3FORM:Form +/
%Super = create G3SEG:CustomSegment(&_form);
end-method;
method GetSubpage
/+ Returns String +/
/+ Extends/implements G3SEG:Segment.GetSubpage +/
Return "G_MYCUST_SEG";
end-method;
method JoinToForm_HE
/+ Extends/implements G3SEG:CustomSegment.JoinToForm_HE +/
Local Rowset &rsCustData = GetLevel0()(1).GetRowset(Scroll.G_CUST_DATA);
%This.JoinRowset(&rsCustData);
end-method;
get name
/+ Returns String +/
/+ Extends/implements G3SEG:Segment.name +/
Return "MyCustomSegment";
end-get;
Update Cloned Framework Programs
Some of the programs cloned with the G3 Form component and page have changed in 3.50 Helium. These must be updated to use the new 3.50 version. The following lists the programs that must be replaced.
Program Type | Program | Copy To |
---|---|---|
Component | G3FORM_FL PreBuild | Custom component |
Component | G3FORM_FL PostBuild | Custom component |
Page | G3FORM_FL Activate | Custom page |
Custom Segment Updates
The main difference between 3.50 Standard and Helium is how the data is stored. In Standard, custom segment data is stored in the form’s mRowset
while Helium stores it in a Json data structure. This table summarizes the differences:
Development Task | 3.50 Standard | 3.50 Helium |
---|---|---|
Class declaration | Extend G3SEG:Segment | Extend G3SEG:CustomSegment |
Include custom segment’s data in the form’s data | Implement method JoinToForm | Implement method JoinToForm_HE |
Serialize custom data | Implement method SavePreChangeHook | Not required |
Notice Helium does not require a SavePreChangeHook
method for custom data to be saved. By inheriting the CustomSegment
class this custom class leverages uniform framework processing that takes care of serialization.
Standard
import G3SEG:Segment;
import G3FORM:Form;
class MyCustomSegment extends G3SEG:Segment method MyCustomSegment(&_form As G3FORM:Form); method GetSubpage() Returns string; method JoinToForm(); method SavePreChangeHook(); property string name get; end-class;
Component G3FORM:Form &G3FRM;
method MyCustomSegment /+ &_form as G3FORM:Form +/
%Super = create G3SEG:Segment(&_form);
end-method;
method GetSubpage /+ Returns String +/ /+ Extends/implements G3SEG:Segment.GetSubpage +/
Return "G_MYCUST_SEG";
end-method;
method JoinToForm /+ Extends/implements G3SEG:Segment.JoinToForm +/
Local Rowset &rsCustData = GetLevel0()(1).GetRowset(Scroll.G_CUST_DATA);
%This.Form.mRowset = CreateRowset(%This.Form.mRowset, &rsCustData);end-method;
method SavePreChangeHook /+ Extends/implements G3SEG:Segment.SavePreChangeHook +/
Local Rowset &rsCustData = GetLevel0()(1).GetRowset(Scroll.G_CUST_DATA);
&rsCustData.CopyTo(%This.Form.mRowset.GetRow(1).GetRowset(Scroll.G_CUST_DATA));end-method;
get name /+ Returns String +/ /+ Extends/implements G3SEG:Segment.name +/
Return "MyCustomSegment";
end-get;
Helium
import G3SEG:CustomSegment;
import G3FORM:Form;
class MyCustomSegment extends G3SEG:CustomSegment method MyCustomSegment(&_form As G3FORM:Form); method GetSubpage() Returns string; method JoinToForm_HE(); property string name get; end-class;
Component G3FORM:Form &G3FRM;
method MyCustomSegment /+ &_form as G3FORM:Form +/
%Super = create G3SEG:CustomSegment(&_form);
end-method;
method GetSubpage /+ Returns String +/ /+ Extends/implements G3SEG:Segment.GetSubpage +/
Return "G_MYCUST_SEG";
end-method;
method JoinToForm_HE /+ Extends/implements G3SEG:CustomSegment.JoinToForm_HE +/
Local Rowset &rsCustData = GetLevel0()(1).GetRowset(Scroll.G_CUST_DATA);
%This.JoinRowset(&rsCustData);end-method;
get name /+ Returns String +/ /+ Extends/implements G3SEG:Segment.name +/
Return "MyCustomSegment";
end-get;
Non-Refactoring Impact
Not changing the class extension will likely cause an error. If the other refactoring changes are not done, then data will not be serialized or deserialized.
Tips to Find
Review the Custom Segments grid in the form configuration to identify custom segments.
Driver Overrides
Override Custom Segment Driver Programmatically
A custom segment driver can be overridden in two different ways:
- Configuration – Create a new class in the form’s application package that extends an existing custom segment. Once created Form Build will add it to the PeopleCode registry where it is then referenced in the form configuration on the Data page, within the Custom Segments section.
- Programmatically – Retrieve the instantiated custom segment at run-time and use the method
overrideSegmentDriver
.
This section discusses the second option.
A programmatic override is generally done in the FormInit
solution code. In Helium most objects remain in memory only when used, the lifespan of a trip. In subsequent trips they are instantiated into a new object, meaning earlier object changes are lost.
Helium offers a new segment method, overrideSegmentDriver
, which is used to indicate to the framework that the segment should persist the lifespan of the form.
Note: If you are changing what happens when clicking the Next button and you want to still validate the current page, see Validate Page Programmatically for additional information.
method FormInit
Local G3CUSTOM_SEGMENTS:Classic:NavigationButtons &segNav;
&segNav = &G3FRM.Segment("GTNavButtons").overrideSegmentDriver();
If &segNav <> Null Then
&segNav.buttonList.Remove("SAVE");
End-If;
end-method;
Required
In Standard, custom segment objects can be manipulated and any changes will persist the lifespan of the form.
Helium offers a new segment method, overrideSegmentDriver
, which is used to indicate to the framework that the segment should persist throughout the lifespan of the form.
Standard
method FormInit
Local G3CUSTOM_SEGMENTS:Classic:NavigationButtons &segNav; &segNav = &G3FRM.SegmentList.Get("GTNavButtons");
If &segNav
<>
Null Then&segNav.buttonList.Remove("SAVE");
End-If; end-method;
Helium
method FormInit
Local G3CUSTOM_SEGMENTS:Classic:NavigationButtons &segNav; &segNav = &G3FRM.segment("GTNavButtons").overrideSegmentDriver();
If &segNav
<>
Null Then&segNav.buttonList.Remove("SAVE");
End-If;
end-method;
Explanation
Standard keeps objects such as segments in memory for the entirety of the form’s life so there is no need to explicitly register objects as with Helium.
Non-Refactoring impact
Changes made to a segment will be lost after it is used the first time. For example, if the save button is removed in FormInit
when the user navigates to the next page it will appear.
Tips to Find
Search solution code for custom segments, such as being retrieved from &G3FRM.SegmentList
, and identify if the object is being manipulated in any manner where the change would be lost on subsequent trips. If the custom segment override class is not referenced in the Form setup then it doesn’t require refactoring.
Navigation Button Custom Segment Driver Override
The following only applies to versions of GT eForms including and after 3.58.00
.
Version 3.58 of GT eForms allows form builders to change button labels and button display (visible/disabled) using configuration. Prior to 3.58, that had to be done using code by overriding the delivered Navigation Button custom segment driver. Coding a custom segment driver override is still required to change what actually happens when the user clicks a button. See the API document for an example.
Recommended for custom button labels or display
If you are upgrading to GT eForms 3.58 and have an existing eForm that uses a custom segment driver override to show custom buttons or to show/hide the Navigation Buttons, we recommend you refactor your form to use the configuration options to do that instead of using the custom segment driver override. To do that, on the Data tab of the Form Type Setup, switch the custom segment driver for the Navigation Buttons custom segment to the delivered "GT Navigation Button" driver.
Other Patterns
The Helium framework creates managed objects from many of the form configuration items. One implication is these items must be defined more precisely than with Standard. This section contains some common configuration items that require updating in Helium. Forms will error without these updates.
Application Package Hierarchy Parts
Parts (SmartSources, Visual If Parts, Custom Segment Drivers) can only be used on a Form Type in either configuration or code if they are defined in the App Package Hierarchy:
- System App Package
- Family App Package
- Search Set App Package
- Form Type App Package
Required
Standard allowed PPC Parts to be used from any application package. Helium requires the parts to be defined in the Application Package Hierarchy.
Tips to Find
Any PPC Parts that are shared between Form Types, should be moved to the Family, Search Set, or System App Packages.
Saving Form Key Data
Form fields can be added to the search keys so the field can be used in searching for forms, typically in the Update and View tasks. This is typically done in the SearchKeySaveHook
API method.
import G3FORM:Form;
import G3FORM:TAGS:FieldTag;
class FormEvents
method SearchKeySaveHook();
end-class;
Component G3FORM:Form &G3FRM;
method SearchKeySaveHook
Local G3FORM:TAGS:FieldTag &FieldTag;
&FieldTag = &G3FRM.record("PAGEREC").field("MYKEY");
&G3FRM.FormKeyRowset.FieldTagToFormKey(&FieldTag);
end-method;
Required
Standard and Helium have different methods for adding a search key; both are in the FormKeyRowset
class. The Standard method requires a PeopleSoft field as a parameter, while Helium uses a FieldTag. The reason for this is Standard creates PeopleSoft records for each configuration segment in the form, so there is always a record.field
available to pass. Helium no longer creates these records so a new method must be used.
Standard
import G3FORM:Form;
import G3FORM:TAGS:FieldTag;
class FormEvents method SearchKeySaveHook(); end-class;
Component G3FORM:Form &G3FRM;
method SearchKeySaveHook
Local Record &rec = CreateRecord(Record.GSINITIALCHECKB);
Local G3FORM:TAGS:FieldTag &FieldTag = &G3FRM.GetRecord("PAGEREC").GetField("MYKEY"); &rec.GSACCESS_THE_SECON.Value = &FieldTag.Value; &G3FRM.FormKeyRowset.FieldToFormKey(&rec.MYKEY);
end-method;
Helium (same code as example from above)
import G3FORM:Form;
import G3FORM:TAGS:FieldTag;
class FormEvents method SearchKeySaveHook(); end-class;
Component G3FORM:Form &G3FRM;
method SearchKeySaveHook
Local G3FORM:TAGS:FieldTag &FieldTag;
&FieldTag = &G3FRM.record("PAGEREC").field("MYKEY");
&G3FRM.FormKeyRowset.FieldTagToFormKey(&FieldTag);
end-method;
Tips to Find
Search solution code for FieldToFormKey
method.
Using PeopleSoft Think-Time Functions
A few PeopleCode functions behave as think-time functions – those that interrupt the component processor flow to await a user response. Examples of this are DoModalComponent
and MessageBox
(under certain conditions). Helium uses an in-memory data structure that represents the disposition of the form at any given time. Think-time functions can result in the corruption of this data structure and result in data loss.
Optional
Refactoring is only necessary if the think-time function is called within a trip (user event) where data has been changed.
Standard
method OFFICE_Change
Local integer &resp = MessageBox(%MsgStyle_YesNo, "", 0, 0, "Copy to Other Field?");
If &resp = %MsgResult_Yes Then Local string &office = &G3FRM.GetRecord("PAGEREC").GetField("OFFICE").Value; &G3FRM.GetRecord("PAGEREC").GetField("NEW_OFFICE").Value = &office; End-If;
end-method;
Use the two new methods shown below to preserve run-time form data. The method preserveRuntimeForm
must be called before the think-time function and restoreRuntimeForm
afterward. The latter still needs to be called regardless of the user response.
Helium.
Helium
method OFFICE_Change
&G3FRM.preserveRuntimeForm();
Local integer &resp = MessageBox(%MsgStyle_YesNo, "", 0, 0, "Copy to Other Field?");
If &resp = %MsgResult_Yes Then Local string &office = &G3FRM.record("PAGEREC").field("OFFICE").Value; &G3FRM.record("PAGEREC").field("NEW_OFFICE").Value = &office; End-If;
&G3FRM.restoreRuntimeForm();
end-method;
Tips to Find
Search solution code for think-time functions. See PeopleBooks for a complete list of these functions.
Invalid Object References
Instances of API Objects may lose their reference in memory as the result of calling a think-time function. In the example below a reference is made to a rowset tag object at the beginning of the solution method. When the think-time function is called the rowset tag object’s pointer in memory is lost. The remedy to this problem is to reassign the object after the think-time call.
method GO_PB_Change
Local G3FORM:TAGS:RowsetTag &rowsetTag = &G3FRM.rowset("GRID01");
Local integer &response = MessageBox(%MsgStyle_YesNo, "", 0, 0, "Flush the Grid?");
If &response = %MsgResult_Yes Then
&rowsetTag = &G3FRM.rowset("GRID01"); /* reconstruct object */
&rowsetTag.flush();
End-If;
end-method;
Data Pool Clean-up
Required
Helium is less forgiving with the configuration of DataPool records, eliminating ambiguity and requiring exactness and consistency.
Custom Load
DataPool records that have a Custom Load cannot have any keys mapped to a grid field. This configuration will result in an error when the form opens:
This issue can be recognized by the error thrown:
If a solution contains this setup, you will need to either remove the grid segment field manually so the configuration should look like this, or run the following script:
You can also run the following script to remove any grid-based key mappings:
UPDATE PS_G3DATA_POOL_FLD SET G3FLD_ALIAS = ' '
WHERE G3FIELD_VAL_TYPE = 'FRM'
AND EXISTS (SELECT 'X' FROM PS_G3DATA_POOL_REC, PS_G3SEGMENT_DEF, PS_G3SEGMENT_FLDS
WHERE PS_G3DATA_POOL_REC.G3DATA_POOL_CLOAD <> ' '
AND PS_G3DATA_POOL_REC.G3POOL_ID = PS_G3DATA_POOL_FLD.G3POOL_ID
AND PS_G3DATA_POOL_REC.G3POOL_RECALIAS = PS_G3DATA_POOL_FLD.G3POOL_RECALIAS
AND PS_G3DATA_POOL_REC.RECNAME = PS_G3DATA_POOL_FLD.RECNAME
AND PS_G3SEGMENT_DEF.G3SEGMENT_TYPE = 'G'
AND PS_G3SEGMENT_DEF.G3SEGMENT = PS_G3SEGMENT_FLDS.G3SEGMENT
AND PS_G3SEGMENT_DEF.G3REC_ALIAS || '.' || PS_G3SEGMENT_FLDS.FIELDNAME = PS_G3DATA_POOL_FLD.G3FLD_ALIAS);
All Keys Must Be Mapped
In Standard, the Context drop-down for the lowest key field, or special fields could be empty.
In Helium, this will cause an error such as this:
This script is an option for updating a blank Context of a DataPool key:
UPDATE PS_G3DATA_POOL_FLD
SET G3FIELD_VAL_TYPE = 'GEN'
WHERE G3FIELD_VAL_TYPE = ' ';
Context must always have value. For special fields, the key can be mapped to an empty constant.
Accessing Another Form’s Data
To instantiate another form object requires using FormFactory
methods. In Helium two form objects cannot be instantiated at the same time. This is because of the use of component-scoped data structures. Having two G3FORM:Form
objects instantiated simultaneously would corrupt the component data structures because only one Form object can use these at a time.
To access another form’s data, follow these steps:
- Instantiate a
FormFactory
object - Use
FormFactory
methodsaveFormState
to preserve component variables - Get the other form object from
FormFactory
’sinitFormDataOnly
method, passing in the external form id. - Set the other Form object’s
contextIndependent
property to true - The local form object cannot be used simultaneously with the current form. Any external form data needed for later must be copied to local variables using the standard data access methods (Tag Tree API).
- Use
FormFactory
methodrestoreFormState
to restore component variables for the original form. - Use local variables from step 4 with the current form.
Local string &formId = "100123";
Local G3FORM:FormFactory &FF = create G3FORM:FormFactory(%UserId);
&FF.saveFormState();
Local G3FORM:Form &otherForm = &FF.initFormDataOnly(&formId, Null);
&otherForm.contextIndependent = True;
Local G3FORM:TAGS:RecordTag &otherRecTag;
&otherRecTag = &otherForm.record("PAGEREC");
Local string &id = &otherRecTag.field("G3ID").Value;
Local string &data = &otherRecTag.field("G3DATA").Value;
&FF.restoreFormState();
Local G3FORM:TAGS:RecordTag &RecTag;
&RecTag = &G3FRM.record("PAGEREC");
&RecTag.field("G3ID").Value = &id;
&RecTag.field("G3DATA").Value = &data;
Required
Standard
Local string &formId = '100123';
Local G3FORM:FormFactory&FF = create G3FORM:FormFactory(%UserId);
Local G3FORM:Form &otherForm = &FF.View(&formId, null); &paForm.contextIndependent = True;
Local G3FORM:TAGS:RecordTag &RecTag; &RecTag = &G3FRM.GetRecord("PAGEREC");
Local G3FORM:TAGS:RecordTag &otherRecTag; &otherRecTag = &otherForm.GetRecord("PAGEREC");
&RecTag.GetField("G3ID").value = &otherRecTag.GetField("G3ID").value; &RecTag.GetField("G3DATA").value = &otherRecTag.GetField("G3DATA").value;
Helium
Local string &formId = '100123';
Local G3FORM:FormFactory&FF = create G3FORM:FormFactory(%UserId); &FF.saveFormState();
Local G3FORM:Form &otherForm = &FF.initFormDataOnly(&formId, null); &otherForm.contextIndependent = True;
Local G3FORM:TAGS:RecordTag &otherRecTag; &otherRecTag = &otherForm.GetRecord("PAGEREC");
Local string &id = &otherRecTag.field("G3ID").Value; Local string &data = &otherRecTag.field("G3DATA").Value;
&FF.restoreFormState();
Local G3FORM:TAGS:RecordTag &RecTag; &RecTag = &G3FRM.GetRecord("PAGEREC"); &RecTag.field("G3ID").value = &id; &RecTag.field("G3DATA").value = &data;
Tips to Find
Search solution code for references to the Form Factory. Determine if another form object is being instantiated from the Form Factory for use with current form (&G3FRM).
Global &FormFactory
Optional
In Standard the global variable &FormFactory
was always available for a user’s session. In Helium this variable is now set to null once &G3FRM
is instantiated.
If &FormFactory
is used in solution code, consider accessing other forms by another means, such as described in the prior section.
Nonexistent RowsetTags
Required
In Standard, Rowset Tags that are obsolete are ignored. For example, solution code was added early in form development to access a Rowset Tag and over time the Tag was renamed, or the segment was removed from configuration.
Helium will throw an error if it cannot find a Rowset, or Record Tag in the internal data structure (Tag Tree). Otherwise this error will fire:
These types of errors are best handled passively as it is time-consuming to cross reference all Rowset Tags in solution code to the form configuration.
GTFormLog
and GTVisualizer
Custom Segment Overrides
Required
In 3.30 both the Form Log and Visualizer custom segments reference a work record field, XAXIS_GRPBOX
, which encompasses the subpage. This field has been replaced with G3FORMLOG_GRPBX
for the Form Log, and G3AWEVIZ_GRPBOX
for the Visualizer.
If one of these custom segments has been extended and the field XAXIS_GRPBOX
field referenced, then the code will need to be updated to reference the new field.
Standard
method paint /+ Extends/implements G3CUSTOM_SEGMENTS:Classic:FormLog.Paint +/ %Super.Paint(); GetLevel0()(1). G3FORMLOG_DRV.XAXIS_GRPBOX.Visible = false; end-method;
Helium
method paint /+ Extends/implements G3CUSTOM_SEGMENTS:Classic:FormLog.Paint +/ %Super.Paint(); GetLevel0()(1). G3FORMLOG_DRV.G3FORMLOG_GRPBX.Visible = false; end-method;
Tips to Find
Search solution code for XAXIS_GRPBOX
; these will be in custom segment override classes.
Segment FieldList
Optional
In Standard all of the display fields (GField) are stored in an Object Hash Table as a property of the segment, such as G3SEG:ConfigSegment
. This hash table is not populated for Helium and an alternate data structure does not exist.
If the FieldList
is used in Standard the solution code must be changed to reference GFields individually as described in Field Display Control.
Standard
method PaintHook Local G3SEG:ConfigSegment &Seg = &G3FRM.SegmentList.Get("MYSEGMENT");
&Seg.FieldList.Get("MYFIELD").LABEL = "New Label";
&Seg.Paint();
end-method;
Helium
method PaintHook Local G3SEG:ConfigSegment &Seg = &G3FRM.SegmentList.Get("MYSEGMENT");
Local G3SEG:GField &GF; &GF = &G3FRM.record("PAGEREC").field("MYFIELD").getGField("MYSEGMENT");
&GF.setLabel("New Label");
&Seg.Paint();
end-method;
Tips to Find
Search solution code for the FieldList
property.
Appendix 1: Event/Hook Flow
This section explains the order in which the framework processes events and hooks. It also explains which user actions trigger these events and hooks. Each table below pertains to a specific user action and contains the events and hooks triggered by that action.
User Action: Opens a form
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Data | E_FORMLOAD | Form Load | Framework | Form | ADD task - Prepopulate form fields based on configuration, sets initial version of data UPD, EVL, VWS task - Loads saved Form data (including custom segment data) |
2 | Data | E_FORMLOAD | Form Validate | Framework | Form | Validates initial data against lookups and configured validations (so invalid fields can be marked in red in paint later) |
3 | Data | E_COMPDATA | Custom Segment LoadData/Deserialize | Framework/Solution Part | Each Custom Segment on the Form | Fires the driver Custom Segment LoadData method to default new or load saved data (Deserialize) for the custom segment into the component buffer |
4 | Data | N/A | Form Init | Solution Hook | Form | Hook to set form data after a user navigates to a form |
User Action: Navigates to a Page
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Display | E_INITIAL_PAINT | Page Paint | Framework | Current Page | Framework paints the page |
2 | Display | E_INITIAL_PAINT | Config Segment Paint | Framework | Each Config Segment on the Page | Framework adds and paints the segment on the page |
3 | Display | E_INITIAL_PAINT | Config Segment Field Paint | Framework | Each Field on the Config Segment | Framework adds and paints the field on the segment |
4 | Display | E_INITIAL_PAINT | Config Segment Field Paint Hook | Solution Hook | Each Field on the Config Segment | Hook to change a field's display each time the framework paints the field |
5 | Display | E_INITIAL_PAINT | Config Segment Paint Hook | Solution Hook | Each Config Segment on the Page | Hook to change a segment's display each time the framework paints the segment or a segment paint hook's annotated depedency changes |
6 | Display | E_INITIAL_PAINT | Custom Segment Paint | Framework/Solution Part | Each Custom Segment on the Page | Framework calls the custom segment driver Paint method |
7 | Display | E_INITIAL_PAINT | Page Paint Hook | Solution Hook | Current Page | Hook to change a page's display each time the framework paints the page or a page paint hook's annotated dependency changes |
8 | Display | E_POSTNAV | Page PostNav | Framework | Current Page | Framework fires first-time logic for a page |
9 | Display | E_POSTNAV | Config Segment PostNav | Framework | Each Segment on the Page | Framework fires first-time logic for a segment |
10 | Display | E_POSTNAV | Config Segment PostNav Hook | Solution Hook | Each Config Segment on the Page | Hook to change a segment's display the first time the user navigates to a page with the segment |
11 | Display | E_POSTNAV | Custom Segment PostNav | Framework/Solution Part | Each Custom Segment on the Page | Framework calls the custom segment driver PostNav method |
12 | Display | E_POSTNAV | Page PostNav Hook | Solution Hook | Current Page | Hook to change a page's display the first time the user navigates to a page |
User Action: Changes a Field
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Data | E_FIELDCHANGE_DATA | Validation | Framework | Current Field | Validates the field format and prompt value - other validations are not run if either fails |
2 | Data | E_FIELDCHANGE_DATA | Validation | Framework | Current Field | Configured validations (no guaranteed order between configured validation, ValidationEvent, and FieldEdit Hook) |
2 | Data | E_FIELDCHANGE_DATA | ValidationEvent | Solution Hook | Current Field | Hook to validate a field's value (no guaranteed order between configured validation, ValidationEvent, and FieldEdit Hook) |
2 | Data | E_FIELDCHANGE_DATA | FieldEdit Hook | Solution Hook | Current Field | Hook to validate a field's value (no guaranteed order between configured validation, ValidationEvent, and FieldEdit Hook) |
3 | Data | E_FIELDCHANGE_DATA | Lifecycle | Framework | Current Field | If valid, updates any fields with a dependency on the current field's data (updates will spawn events for those fields) |
4 | Data | E_FIELDCHANGE_DATA | FieldChange Hook | Solution Hook | Current Field | If valid, Hook to update other field values or other data-related logic (updates will spawn events for those fields) |
5 | Data | E_FIELDCHANGE_DATA | Custom Segment ReloadData | Framework/Solution Part | Custom Segment | Reloads data for any custom segments with a dependency on the current field's data (e.g. Attachment rows that become required because its Required Visual If is now true) |
6 | Display | E_FIELDCHANGE_DISPLAY | Paint | Framework/Solution Hook/Part | Any of these with a dependency on the current field: Page Paint Hook Config Segment Paint Config Segment Paint Hook Field Paint Field Paint Hook Custom Segment Paint | Triggers display events in the same order as "Navigate to a Page" above for display elements that are dependent on data that has changed (e.g. a segment that should now show because its visibility VisualIf depends on the changed field and now returns true) |
User Action: Inserts a Row
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Data | E_INSERTROW | InsertRow Hook | Solution Hook | Current RowsetTag | Hook to change the data on an inserted row. This hook can be used to recalculate (e.g. to set a column segment Total field) but it is highly recommended you use a subscription pattern with a SmartSource or VisualIf instead. |
2 | Data | E_INSDELROW_ALL_DATA | InsertRow All Data | Framework | Any of these with a dependency on the current RowsetTag: FieldTag RowsetFieldTag CustomSegmentData | Lifecycles any fields or reloads data for custom segments that depend on the number of rows on the grid (e.g. a column field lifecycled from SmartSource that calculates a total) |
3 | Display | E_INSDELROW_ALL_DISPLAY | InsertRow All | Framework | Any display elements (Fields, Config Segments, Custom Segments) with a dependency (typically through a Visual If) on the current RowsetTag | Paints any display elements that depend on the number of rows on the grid (e.g. a column field that is visible based on a Visual If that calculates a total) |
4 | Display | E_INSERTROW_GRID | InsertRowGrid | Framework | Current GridSegment Row | If InsertRow is called from PPC and user is on the page with the grid, inserts the row in the grid |
5 | Display | E_INSERTROW_DISPLAY | InsertRowDisplay | Framework | Current GridSegment Row Fields | Paints the inserted row (User or PPC Insert) |
User Action: Deletes a Row
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Data | E_DELETEROW | DeleteRow Hook | Solution Hook | Current RowsetTag | Hook to prevent deleting the current row, fires before row is deleted (note for GT: Framework actually triggers this later, but conceptually happens first) |
2 | Data | E_INSDELROW_ALL_DATA | DeleteRow All Data | Framework | Any of these with a dependency on the current RowsetTag: FieldTag RowsetFieldTag CustomSegmentData | Lifecycles any fields or reloads data for custom segments that depend on the number of rows on the grid (e.g. a column field lifecycled from SmartSource that calculates a total) |
3 | Display | E_INSDELROW_ALL_DISPLAY | DeleteRow All Display | Framework | Any display elements (Fields, Config Segments, Custom Segments) with a dependency (typically through a Visual If) on the current RowsetTag | Paints any display elements that depend on the number of rows on the grid (e.g. a column field that is visible based on a Visual If that calculates a total) |
4 | Display | E_DELETEROW_GRID | DeleteRowGrid | Framework | Current GridSegment Row | If DeleteRow is called from PPC and user is on the page with the grid, deletes the row from the grid |
5 | Display | E_INSERTROW_DISPLAY | InsertRowDisplay | Framework | Current GridSegment Row Fields | Paints the PS inserted row if all rows are deleted |
User Action: Leaves a Page
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Data | E_PRENAV | Segment PreNav | Framework | Each Segment on the Page | Validates all required fields (config) on the segment have a value (analysis only) If custom segment, runs driver validation logic (throws an error) |
2 | Data | E_PRENAV | ValidationEvent Hook | Solution Hook | Each Segment on the Page | Hook to run custom validation related to the segment |
2 | Data | E_PRENAV | Segment PreNav Hook | Solution Hook | Each Segment on the Page | Hook to run custom validation related to the segment |
3 | Data | E_PRENAV | Page PreNav | Framework | Current Page | Validates all required fields on the page have a value (throws an error) |
4 | Data | E_PRENAV | Page PreNav | Framework | Current Page | Configured validations (no guaranteed order between configured validation, ValidationEvent, and PreNav Hook) |
4 | Data | E_PRENAV | ValidationEvent Hook | Solution Hook | Current Page | Hook to run custom validation related to the page (no guaranteed order between configured validation, ValidationEvent, and PreNav Hook) |
4 | Data | E_PRENAV | Page PreNav Hook | Solution Hook | Current Page | Hook to run custom validation related to the page (no guaranteed order between configured validation, ValidationEvent, and PreNav Hook) |
User Action: Saves a Form
Order | Data/Display | Event | Event Task | Event Type | Scope | Event Description |
---|---|---|---|---|---|---|
1 | Data | E_SAVEPRECHANGE | Custom Segment SavePreChange | Framework/Solution Part | Each Custom Segment on the Form | Fires the driver Custom Segment driver SavePreChange method to validate the custom segment data prior to saving the form |
1 | Data | E_SAVEPRECHANGE | Form SavePreChange | Framework | Form | Configured validations (no guaranteed order between configured validation, ValidationEvent, and SavePreChange Hook) |
1 | Data | E_SAVEPRECHANGE | ValidationEvent Hook | Solution Hook | Form | Hook to run custom validation prior to saving a form when a user takes action on a form (submit, resubmit, deny, withdraw, save). |
1 | Data | E_SAVEPRECHANGE | Form SavePreChange Hook | Solution Hook | Form | Hook to run custom validation prior to saving a form when a user takes action on a form (submit, resubmit, deny, withdraw, save). |
2 | Data | E_FORMSAVE | Custom Segment Save/Serialize | Framework/Solution Part | Each Custom Segment on the Form | Fires the driver Custom Segment Save method to save the custom segment data to a table or Serialize to the form JSON (either calling %Super.Save() or Serialize directly) |
3 | Data | N/A | SearchKeySave Hook | Solution Hook | Form | Hook to set form keys |
4 | Data | N/A | SearchKeySave | Framework | Form | Saves form keys to G3FORMKEY table |
Appendix 2: Tech Trigger
The Tech Trigger is a drop-down list and button on the top right of an opened form:
These fields only show if the user has the role GT eForms Designer.
The Tech Trigger can be used to programmatically access and trigger form properties and methods while the form is in use. It is a troubleshooting tool for developers. There are three delivered options with the Tech Trigger:
Form Type - simply shows the name of the form type Initial Paint – Repaints the current page Log JSON – Writes the form json to the Debug Log if debugging is enabled |
---|
Tech Trigger options can be developed and added to the dropdown by adding code to G_TECH_TRIGGER:TechTrigger
. Refer to the instructions in that class for more guidance.
Appendix 3: Internal GT Use
NOTE: This section is for GT Internal use and clients do not need to know the information contained here. Nonetheless, it is available for reference.
Field Driver Override
Field Driver Overrides are a custom extension of the GField
as identified through the FieldTag
. Overrides allow for greater control of this class. Typically, these overrides are defined when the form is initialized, such as in the FormInit
event so they are used by the framework when painting.
As with custom segment drivers that are overridden programmatically, in Helium the GField
override object is lost once the trip ends. In other words, a field driver is overridden in FormInit
and used in the initial painting of the page. After painting the page the trip is over and the object no longer persists. The next use of the GField
will use the standard implementation.
The Standard override is supported in Helium; internally the framework will persist the override between trips:
overrideGFieldDriver
– override the GField
object for a particular segment associated with the FieldTag
Helium offers these new methods:
overrideGFieldDrivers
– override the GField
object for all segments associated with the FieldTag
removeGFieldOverride
– remove a GField
driver for a specific segment associated with the FieldTag
removeGFieldOverride
– remove all GField
drivers associated with the FieldTag
Note: This solution is also applicable to grid segment field objects (GFieldGrid
).
method FormInit
Local G3FORM:TAGS:FieldTag &FieldTag;
&FieldTag = &G3FRM.record("PAGEREC").field("G3DATA");
&FieldTag.overrideGFieldDriver("MYSEGMENT", "G_FORM_MYFORM:FieldOverrides:G3Data");
end-method;
Examples to Add
Example 2 – Hiding the Save button in the GT Navigation Buttons custom segment when something in the form changes.
import G3FORM:Form;
import G3CUSTOM_SEGMENTS:Classic:NavigationButtons;
class GTNavButtons
method PaintHook();
end-class;
Component G3FORM:Form &G3FRM;
/*@Dependency(Type="SmartSource", Value="[PAGEREC:G_HIDE_CHK]")*/
method PaintHook
Local G3CUSTOM_SEGMENTS:Classic:NavigationButtons &segNav;
&segNav = &G3FRM.SegmentList.Get("GTNavButtons");
If &G3FRM.record("PAGEREC").field("G_HIDE_CHK").Value = "Y" Then
&segNav.dRec.G3SAVE_PB.Visible = False;
Else
&segNav.dRec.G3SAVE_PB.Visible = True;
End-If;
end-method;
The only significant difference between Standard and Helium is the latter requires an annotation because it is dependent on a form field's value.
Standard
import G3FORM:Form;
import G3CUSTOM_SEGMENTS:Classic:NavigationButtons;
class GTNavButtons method PaintHook(); end-class; Component G3FORM:Form &G3FRM;
method PaintHook
Local G3CUSTOM_SEGMENTS:Classic:NavigationButtons &segNav; &segNav = &G3FRM.SegmentList.Get("GTNavButtons");
If &G3FRM.record("PAGEREC").field("G_HIDE_CHK").Value = "Y" Then &segNav.dRec.G3SAVE_PB.Visible = False; Else &segNav.dRec.G3SAVE_PB.Visible = True; End-If;
end-method;
Helium
import G3FORM:Form;
import G3CUSTOM_SEGMENTS:Classic:NavigationButtons;
class GTNavButtons method PaintHook(); end-class; Component G3FORM:Form &G3FRM;
/@Dependency(Type="SmartSource", Value="[PAGEREC:G_HIDE_CHK]")/ method PaintHook
Local G3CUSTOM_SEGMENTS:Classic:NavigationButtons &segNav; &segNav = &G3FRM.SegmentList.Get("GTNavButtons");
If &G3FRM.record("PAGEREC").field("G_HIDE_CHK").Value = "Y" Then &segNav.dRec.G3SAVE_PB.Visible = False; Else &segNav.dRec.G3SAVE_PB.Visible = True; End-If;
end-method;
Non-Refactoring impact
Without the annotation, the PaintHook
will not fire when the field changes.
Tips to identify whether your eForm solution is affected by this
Examine segment PaintHook
methods and determine if something within is dependent on a data element, usually a form field. Upon testing, if the desired behavior is not working then annotate the method with a dependency.