Skip to main content

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

IMPORTANT NOTE

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>")*/
NOTE

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.

A screenshot of the network visualizer showing the ID of a Visual If

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:

  1. The number of rows in a grid (e.g. a Visual If Part that returns true if a grid has 3 or more rows)
  2. 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)
  3. 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.

A screenshot of the &#39;Field Details&#39; section of Form Setup showing the &#39;S G Total Amount&#39; SmartSource selected under &#39;Update when This Source Changes&#39;

This is what the Network Visualizer will look like for this scenario:

A screenshot of the Network Visualizer showing the dependencies for the aforementioned setup with the &#39;S G Total Amount&#39; SmartSource

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.

Important Warning for this Complex Example

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.

Note: sample code for reference, not tested
/*@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

HeliumStandard (Included for reference/comparison)
After creating a new PPC PartForm Type BuildLoad Custom Parts
After creating a new PPC HookForm Type BuildNo Process
After making changes to a PPC PartForm Type Build, only if adding, changing, or removing an annotation.No Process
After making changes to a PPC HookForm Type Build, only if adding, changing, or removing an annotation.No Process
After removing a PPC PartForm 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 HookForm Type BuildNo Process
Code Dependencies using PSPCMTXT
Important!

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 ⬇️

ERROR

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.

  1. 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 in PSPCMTXT:
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 '\';
NOTE

This will include all G\_ objects (form solution code). Change the WHERE criteria as needed.

  1. The resulting list will indicate which object need to be resynced. OBJECTVALUE1 is the application package for the missing PeopleCode.

  2. To resync, create a new project in Application Designer.

  3. 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.

  4. After the application packages and PeopleCode instances have been added to the project, export the project to a file.

  5. Import the project from a file into either the current environment or a target environment

    1. Import the project from the file

    2. Check the box to use the project definition from file and hit OK (if re-importing into the current environment)

    3. Check the box in the bottom left corner that says "Compile PeopleCode after Import" and then hit Copy

      A screenshot of the checkbox that in App Designer that says &#39;Compile PeopleCode after Import&#39;

  6. 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.

  1. Run the UPGPTHASH App Engine – this will sync all PPC in the environment between PSPCMPROG and PSPCMTXT. If it does not appear within the PIA, it can be run directly from App Designer
  2. Open the class in app designer, add a space and save

Please see these links for more information:

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.

A screenshot of the GT Dev Debug Tool menu

Helpful Tips

  1. Download the log to produce an Excel document as the log is comma delimited.
  2. Turn on filtering for the columns for ease in narrowing down the focus.
  3. 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
Refactor Standard to Helium

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 and FieldEdit 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 PatternFieldChange MethodFieldEdit MethodPropagate DataPropagate Display *
&fieldTag.Value = "test";YesYesYesYes
&fieldTag.setValueOnly("test");NoNoNoNo
&fieldTag.setValuePropagateData("test");YesYesYesNo
&fieldTag.setValuePropagateDisplay("test");NoNoNoYes
&fieldTag.SetValueSuppressChangePropagate("test");NoYesYesYes
&fieldTag.SetValueSuppressEditPropagate("test");YesNoYesYes
&fieldTag.SetValueSuppressEventsPropagate("test");NoNoYesYes

* Display won't fire if a form is instantiated and updated using PPC outside of the eForm component context.

Refactor Standard to Helium

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;
Refactor Standard to Helium

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.

A screenshot of a Segment Setup menu in Form Setup showing a segment with one field titled &#39;GSRICH_TEXT_EX&#39;

A screenshot of the Field Details menu in Form Setup showing the option selected that says &#39;Load/Save Rich Text as HTML&#39;

Refactor Standard to Helium
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.

NOTE

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

Refactor Standard to Helium

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
Refactor Standard to Helium

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");
Refactor Standard to Helium

Recommended

Standard

&G3FRM.SegmentList.Get("SegmentName");

Helium

&G3FRM.segment("SegmentName");

Form mRowset

Refactor Standard to Helium

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:

  1. For Hooks, use parameter &_rowTag As G3FORM:TAGS:RowTag
    1. FieldChange
    2. FieldEdit
  2. For Parts, use parameter &_rowNum As Integer
    1. SetSortDropDownList
    2. SmartSource
    3. 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;
Refactor Standard to Helium

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;

Refactor Standard to Helium

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:

  1. Use new RowsetEvent Class InsertRow and DeleteRow (Highly Recommended)
  2. Update InsertRowHook to account for conceptual change with &G3FRM.CurrentRowNumber
Option 1 - Use the new RowsetEvent class (Highly Recommended)
TypeStandardHelium
Class[Package]:[SegmentEvents]:SegmentName[Package]:RowsetEvents:[Rowset Tag Name]
Insert MethodInsertRowHookInsertRow
Delete MethodDeleteRowHookDeleteRow
ParameterNone&_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.

A screenshot of the General Tab of GT eForms with the &#39;Disable Lifecycle Field Events&#39; checkbox checked

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 PropertyPurpose
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.
Refactor Standard to Helium

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;
Refactor Standard to Helium

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:

  1. AppPackage – This option just communicates to the functional users that the drop-down options are generated by code, specifically this SetSortDropDownList hook.
  2. 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.
  3. 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.
  4. 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
Refactor Standard to Helium

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

Refactor Standard to Helium

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

Refactor Standard to Helium

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.

StandardHelium
1) Solution Page PostNav1) GT Framework Page PostNav
2) GT Framework Page PostNav2) GT Framework Page Paint
3) Solution Page Paint3) Solution Page Paint
4) GT Framework Page Paint4) 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 event

class 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 page

class 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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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();
Refactor Standard to Helium

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();
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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".

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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;
Refactor Standard to Helium

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();
Refactor Standard to Helium

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:

  1. Create Population method in the LogicParts class of the form’s application package.
  2. Run the Form Build process or the advanced steps for updating the registry and creating custom parts.
  3. 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.
  4. On the Form Field Links of the data pool configuration:
    1. Map the RowsetTag to the DataPool record in the Initialize column.
    2. Map the Form fields to the DataPool fields
  5. 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:

A screenshot of the Data Pool menu showing a Data Pool record with a Custom Load called &#39;EmployeeDependents&#39; in the &#39;Custom Load&#39; field

Map the Form fields to initialize from the Data Pool fields:

A screenshot of the &#39;Form Field Links&#39; menu in the Data Pool with some fields configured to initialize with values from the Custom Load mentioned in the previous example

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;
note

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:

  1. Create a PeopleSoft subpage with the custom records, fields, etc.
  2. Clone PeopleSoft page G3FORM_PAGE_FL (and PeopleCode)
  3. Clone PeopleSoft component G3FORM_PAGE_FL (and PeopleCode)
  4. Put custom component into a menu
  5. Place created subpage in step one into page in step two.
  6. Place cloned page in step two into cloned component in step three.
  7. Create class in the Form’s application package. The class has these requirements:
    1. Must extend G3SEG:CustomSegment
    2. Constructor must instantiate the super class
    3. Have a method, GetSubpage, which returns the name of the subpage in step one.
    4. Have a getter string property, name, which returns the name of the segment; this can be any descriptive value
    5. [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.
  8. Register custom segment override by running Form Build.
  9. Identify in Form Setup the custom segment override on the Data page
  10. 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;
Refactor Standard to Helium

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 TypeProgramCopy To
ComponentG3FORM_FL PreBuildCustom component
ComponentG3FORM_FL PostBuildCustom component
PageG3FORM_FL ActivateCustom 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 Task3.50 Standard3.50 Helium
Class declarationExtend G3SEG:SegmentExtend G3SEG:CustomSegment
Include custom segment’s data in the form’s dataImplement method JoinToFormImplement method JoinToForm_HE
Serialize custom dataImplement method SavePreChangeHookNot 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:

  1. 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.
  2. 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;
Refactor Standard to Helium

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.

VERSION-SPECIFIC

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.

Refactor a Pre-3.58 form to 3.58

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
Refactor Standard to Helium

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;
Refactor Standard to Helium

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.

Refactor Standard to Helium

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

Refactor Standard to Helium

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:

A screenshot with an example of the invalid configuration; a Data Pool record with a custom load and a key mapped to a grid field

This issue can be recognized by the error thrown:

A screenshot with an example of the error message a user may expect to see if the above invalid configuration is used

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:

A screenshot with the ideal method of handling this situation via configuration, with the record key mapped to a blank constant value

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.

A screenshot demonstrating the aforementioned setup, with an EFFDT key (the lowest key in the Data Pool record) with a blank context in the Data Pool menu

In Helium, this will cause an error such as this:

An screenshot of the error that appears when a Helium Form Type has the above configuration

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.

A screenshot with an example of a data pool record that has Context values specified for ALL keys, but one key (EFFDT) is mapped to a blank 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:

  1. Instantiate a FormFactory object
  2. Use FormFactory method saveFormState to preserve component variables
  3. Get the other form object from FormFactory’s initFormDataOnly method, passing in the external form id.
  4. Set the other Form object’s contextIndependent property to true
  5. 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).
  6. Use FormFactory method restoreFormState to restore component variables for the original form.
  7. 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;
Refactor Standard to Helium

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

Refactor Standard to Helium

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

Refactor Standard to Helium

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:

An example of the error that appears if a Helium engine can not find a Rowset or Record Tag

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

Refactor Standard to Helium

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

Refactor Standard to Helium

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

OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DataE_FORMLOADForm LoadFrameworkFormADD task - Prepopulate form fields based on configuration, sets initial version of data
UPD, EVL, VWS task - Loads saved Form data (including custom segment data)
2DataE_FORMLOADForm ValidateFrameworkFormValidates initial data against lookups and configured validations (so invalid fields can be marked in red in paint later)
3DataE_COMPDATACustom Segment LoadData/DeserializeFramework/Solution PartEach Custom Segment on the FormFires the driver Custom Segment LoadData method to default new or load saved data (Deserialize) for the custom segment into the component buffer
4DataN/AForm InitSolution HookFormHook to set form data after a user navigates to a form
OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DisplayE_INITIAL_PAINTPage PaintFrameworkCurrent PageFramework paints the page
2DisplayE_INITIAL_PAINTConfig Segment PaintFrameworkEach Config Segment on the PageFramework adds and paints the segment on the page
3DisplayE_INITIAL_PAINTConfig Segment Field PaintFrameworkEach Field on the Config SegmentFramework adds and paints the field on the segment
4DisplayE_INITIAL_PAINTConfig Segment Field Paint HookSolution HookEach Field on the Config SegmentHook to change a field's display each time the framework paints the field
5DisplayE_INITIAL_PAINTConfig Segment Paint HookSolution HookEach Config Segment on the PageHook to change a segment's display each time the framework paints the segment or a segment paint hook's annotated depedency changes
6DisplayE_INITIAL_PAINTCustom Segment PaintFramework/Solution PartEach Custom Segment on the PageFramework calls the custom segment driver Paint method
7DisplayE_INITIAL_PAINTPage Paint HookSolution HookCurrent PageHook to change a page's display each time the framework paints the page or a page paint hook's annotated dependency changes
8DisplayE_POSTNAVPage PostNavFrameworkCurrent PageFramework fires first-time logic for a page
9DisplayE_POSTNAVConfig Segment PostNavFrameworkEach Segment on the PageFramework fires first-time logic for a segment
10DisplayE_POSTNAVConfig Segment PostNav HookSolution HookEach Config Segment on the PageHook to change a segment's display the first time the user navigates to a page with the segment
11DisplayE_POSTNAVCustom Segment PostNavFramework/Solution PartEach Custom Segment on the PageFramework calls the custom segment driver PostNav method
12DisplayE_POSTNAVPage PostNav HookSolution HookCurrent PageHook to change a page's display the first time the user navigates to a page

User Action: Changes a Field

OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DataE_FIELDCHANGE_DATAValidationFrameworkCurrent FieldValidates the field format and prompt value - other validations are not run if either fails
2DataE_FIELDCHANGE_DATAValidationFrameworkCurrent FieldConfigured validations (no guaranteed order between configured validation, ValidationEvent, and FieldEdit Hook)
2DataE_FIELDCHANGE_DATAValidationEventSolution HookCurrent FieldHook to validate a field's value (no guaranteed order between configured validation, ValidationEvent, and FieldEdit Hook)
2DataE_FIELDCHANGE_DATAFieldEdit HookSolution HookCurrent FieldHook to validate a field's value (no guaranteed order between configured validation, ValidationEvent, and FieldEdit Hook)
3DataE_FIELDCHANGE_DATALifecycleFrameworkCurrent FieldIf valid, updates any fields with a dependency on the current field's data (updates will spawn events for those fields)
4DataE_FIELDCHANGE_DATAFieldChange HookSolution HookCurrent FieldIf valid, Hook to update other field values or other data-related logic (updates will spawn events for those fields)
5DataE_FIELDCHANGE_DATACustom Segment ReloadDataFramework/Solution PartCustom SegmentReloads 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)
6DisplayE_FIELDCHANGE_DISPLAYPaintFramework/Solution Hook/PartAny 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

OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DataE_INSERTROWInsertRow HookSolution HookCurrent RowsetTagHook 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.
2DataE_INSDELROW_ALL_DATAInsertRow All DataFrameworkAny 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)
3DisplayE_INSDELROW_ALL_DISPLAYInsertRow AllFrameworkAny display elements (Fields, Config Segments, Custom Segments) with a dependency (typically through a Visual If) on the current RowsetTagPaints 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)
4DisplayE_INSERTROW_GRIDInsertRowGridFrameworkCurrent GridSegment RowIf InsertRow is called from PPC and user is on the page with the grid, inserts the row in the grid
5DisplayE_INSERTROW_DISPLAYInsertRowDisplayFrameworkCurrent GridSegment Row FieldsPaints the inserted row (User or PPC Insert)

User Action: Deletes a Row

OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DataE_DELETEROWDeleteRow HookSolution HookCurrent RowsetTagHook to prevent deleting the current row, fires before row is deleted
(note for GT: Framework actually triggers this later, but conceptually happens first)
2DataE_INSDELROW_ALL_DATADeleteRow All DataFrameworkAny 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)
3DisplayE_INSDELROW_ALL_DISPLAYDeleteRow All DisplayFrameworkAny display elements (Fields, Config Segments, Custom Segments) with a dependency (typically through a Visual If) on the current RowsetTagPaints 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)
4DisplayE_DELETEROW_GRIDDeleteRowGridFrameworkCurrent GridSegment RowIf DeleteRow is called from PPC and user is on the page with the grid, deletes the row from the grid
5DisplayE_INSERTROW_DISPLAYInsertRowDisplayFrameworkCurrent GridSegment Row FieldsPaints the PS inserted row if all rows are deleted

User Action: Leaves a Page

OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DataE_PRENAVSegment PreNavFrameworkEach Segment on the PageValidates all required fields (config) on the segment have a value (analysis only)
If custom segment, runs driver validation logic (throws an error)
2DataE_PRENAVValidationEvent HookSolution HookEach Segment on the PageHook to run custom validation related to the segment
2DataE_PRENAVSegment PreNav HookSolution HookEach Segment on the PageHook to run custom validation related to the segment
3DataE_PRENAVPage PreNavFrameworkCurrent PageValidates all required fields on the page have a value (throws an error)
4DataE_PRENAVPage PreNavFrameworkCurrent PageConfigured validations (no guaranteed order between configured validation, ValidationEvent, and PreNav Hook)
4DataE_PRENAVValidationEvent HookSolution HookCurrent PageHook to run custom validation related to the page (no guaranteed order between configured validation, ValidationEvent, and PreNav Hook)
4DataE_PRENAVPage PreNav HookSolution HookCurrent PageHook to run custom validation related to the page (no guaranteed order between configured validation, ValidationEvent, and PreNav Hook)

User Action: Saves a Form

OrderData/DisplayEvent      Event Task      Event Type                Scope                                                    Event Description                                    
1DataE_SAVEPRECHANGECustom Segment SavePreChangeFramework/Solution PartEach Custom Segment on the FormFires the driver Custom Segment driver SavePreChange method to validate the custom segment data prior to saving the form
1DataE_SAVEPRECHANGEForm SavePreChangeFrameworkFormConfigured validations (no guaranteed order between configured validation, ValidationEvent, and SavePreChange Hook)
1DataE_SAVEPRECHANGEValidationEvent HookSolution HookFormHook to run custom validation prior to saving a form when a user takes action on a form (submit, resubmit, deny, withdraw, save).
1DataE_SAVEPRECHANGEForm SavePreChange HookSolution HookFormHook to run custom validation prior to saving a form when a user takes action on a form (submit, resubmit, deny, withdraw, save).
2DataE_FORMSAVECustom Segment Save/SerializeFramework/Solution PartEach Custom Segment on the FormFires 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)
3DataN/ASearchKeySave HookSolution HookFormHook to set form keys
4DataN/ASearchKeySaveFrameworkFormSaves 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:

A screenshot of the Tech Trigger that appears at the top right of opened forms

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:

A screenshot of the options that appear in the Tech Trigger dropdownForm 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

For GT Internal 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;
Refactor Standard to Helium

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.