Programming Guide : 6. Working with Arrays, Table Fields, and Collections : Arrays : How You Can Manipulate Arrays
 
Share this page                  
How You Can Manipulate Arrays
An application can perform the following array operations:
Add rows
Change the values in all or some of the rows
Delete all or some of the rows in an array
You can choose from a variety of ways to accomplish these tasks:
For a discussion of adding rows, see How You Can Add Rows to an Array.
For a discussion of changing rows, see How You Can Change Values in an Array.
For more information about deleting rows, see How You Can Delete Rows in an Array.
There are several methods of the ArrayObject class that manipulate arrays (such as inserting, removing, or sorting rows) or provide information (such as the number of rows in the array). For a detailed description of these methods, see How You Can Retrieve Array Information, or see the ArrayObject section of the Language Reference Guide online help.
How You Can Use the _RowState Attribute
To determine changes made directly to an array by a user editing a table field, you can use the _RowState attribute for each row in an array. This attribute contains information about the state of each row, indicating the last action performed on the row by the user or by the SetRowDeleted method. Changes made to an existing row by a 4GL script (except any changes resulting from the execution of the SetRowDeleted method) do not affect this attribute.
There are five valid row states:
RS_UNDEFINED
Indicates that:
A row was automatically added to the end of an array when a user, in a table field, set the input focus to the row just beyond the last row or used the down arrow key to move past the last row.
A user selected the Insert Before Current Row operation from a table field's default control button. A row whose state is RS_UNDEFINED cannot change to a state of RS_DELETED. When these rows are deleted by any means, they disappear from the array.
RS_NEW
Occurs when a user types in or otherwise changes an undefined (RS_UNDEFINED) row.
A row whose state is RS_NEW cannot change to a state of RS_DELETED. When these rows are deleted by any means, they disappear from the array.
RS_UNCHANGED
Indicates a row that was added to the array by a 4GL script (by the InsertRow method or assignment statement) and has not yet been changed by the user.
RS_CHANGED
Occurs when a user makes a change, other than a deletion, to an RS_UNCHANGED row. If the user changes the data and then sets it back to its original value, the row's state remains RS_CHANGED.
RS_DELETED
Indicates a row that was initially RS_UNCHANGED or RS_CHANGED and then was marked deleted, either by the program (using the SetRowDeleted method) or by a user (using the table field control button's Delete Current Row or Delete All Rows operation).
A row that has the state RS_DELETED remains in the array with a non-positive row number.
You can force a row to reflect a different state than would occur by default by directly assigning a value to the _RowState attribute. For example, assume a frame remains open after the user clicks the Save button. This frame lets the user continue making changes to values in a table field after saving existing changes. To prevent the same changes from being written to the database the next time the user clicks the Save button, you can set the state of all rows back to RS_UNCHANGED. The following code fragment checks the row state of every row in the custtable array, makes appropriate changes to the database, and resets the value of all row states to RS_CHANGED:
for i = custtable.FirstRow to custtable.LastRow do
  if custtable[i]._rowstate = RS_DELETED then
    repeated delete from customer where acctno =
    :cust_table[i].acctno;
  elseif custtable[i]._rowstate = RS_NEW then
    repeated insert into customer ...
  elseif custtable[i]._rowstate = RS_CHANGED then
    repeated update customer...
  endif;
  commit;
  /*Reset rowstate to prevent handling this row
  ** again on next Save unless data changes
  ** again. */
  custtable[i]._RowState = RS_UNCHANGED;
endfor;
Fence Diagram for Row States and Transitions
The following illustration summarizes the effects of programming statements and user actions on array row states:
The vertical lines represent the starting and ending row states.
The arrows are labeled to indicate the statement or action that causes the state to change.
The numbers in parentheses to the right of each row state indicate the numeric value of the row state constants.
The following statements illustrate a few possibilities:
If a user opens a new row by selecting the Insert New Row operation of a default control button, the row state changes from nonexistent to RS_UNDEFINED.
If the user changes the newly inserted row, the row state changes from RS_UNDEFINED to RS_NEW.
If the 4GL code makes an assignment to the newly inserted row, the row state does not change; therefore the arrow in the fence diagram begins and ends at the same position.
If a row with a row state of RS_NEW is deleted, its row state becomes nonexistent.
If a row with a row state of RS_UNCHANGED is deleted, its row state becomes RS_DELETED.
How You Can Add Rows to an Array
Because creating an array does not populate it, adding rows to the array may be the first task to perform after declaring the array. There are several ways that you can populate an empty array or add additional rows to a populated array:
Add rows by first assignment
Use the InsertRow method
Assign the contents of one array to another
Create a populated array by duplicating an existing array
Allow the user to append to a table field
The following sections describe each of these methods in more detail.
Add Rows by First Assignment
When you make an assignment to a nonexistent row in your 4GL code, OpenROAD creates that row if it is the next row in the array sequence; if it is not, an error occurs. This is an easy way to fill an array.
For example, the following select loop fills the custacct array:
i = 1;
select :custacct[i].acctno = cacctno,
       :custacct[i].custname = cname,
       :custacct[i].acctbalance = acctbal
from customer
begin
    i = i + 1;
end;
These statements perform the following operations:
1. Place the first row that the select returns into the first row of the array.
2. For each row retrieved, increment i (the index into the array) by one.
3. Place the second returned row into the second row in the array (in the second iteration of the loop) and again increment i.
4. Continue this process for each row returned by the select statement.
If any of the array's rows were nonexistent prior to executing the select statement, they are automatically created when the statement is executed.
This method only allows rows to be added to the end of an array or to an empty array. Also, the index must be incremented by one; this method does not work if row numbers are skipped.
When you use this method to add rows to an array that is displayed in a table field, OpenROAD updates the display when the event block containing the select loop completes.
Use the InsertRow Method of the ArrayObject Class
The syntax for this method is:
integer = arrayname.InsertRow([rownumber = integer]
          [, rowobject = object] [,_rowstate = integer]);
arrayname
Specifies the name of the array
object
Specifies a reference variable that points to an object that is either of the same class as, or is a subclass of, the array's class. When the statement is executed, OpenROAD inserts the object into the array at the specified position.
If you do not specify an object, OpenROAD constructs an object whose attributes contain default values and inserts the object into the array. Default values are null for nullable fields, zero for numeric fields and blanks for character fields.
Another way to set default values for a column is by setting default values for the ProtoField of the column. For more information, see TableField, ColumnField, and ProtoField Objects).
The InsertRow method inserts the new row ahead of the row specified by the rownumber parameter and adjusts the numbers of the rows following the new row. For example, if you insert a row into the third row position in the array, the current row 3 becomes row 4. If you do not specify the rownumber parameter, OpenROAD inserts the new row as row 1. To insert a row at the end of the array, set rownumber = arrayname.LastRow+1.
By setting _rowstate, you can set the _rowstate to other than the default RS_CHANGED.
Inserting a default object is the best way to insert an empty row into an array. For example, the following code inserts a blank row at the specified position:
custtable.InsertRow(rownumber = row);
The inserted row is blank because the rowobject parameter is not specified. This technique is especially useful if you display an array in a table field and want the user to enter new data in a particular position in the array.
If you set rowobject to null, 4GL creates the row number in the array, but no object is associated with the new row. This feature provides useful program-control capabilities but should not be used if the array is displayed by a table field.
Assign the Contents of One Array to Another Array
Assume that you have an array variable, custtable, that contains customer addresses and an empty array variable, addresses. The following statement assigns the contents of custtable to addresses:
addresses = custtable;
This statement does not create two distinct objects, but rather two array variables that point to the same set of objects, that is, to the same array. Both custtable and addresses now reference the same array.
Create a Populated Array by Duplicating an Existing Array
If you want two separate array variables so that you can modify the data in one array without affecting the other, you can create a second copy of the array by using the Duplicate method defined for the Object system class.
You can make an exact duplicate of an array, placing a reference to the new array in a new reference variable, provided the new reference variable is of the same class, or a superclass of, the array being copied.
The following example creates a new array object called array2 which is an exact duplicate of array1:
declare
  array1 = array of myclass;
  array2 = array of myclass;
enddeclare
  begin
      /* load values into array1 */
      /* then duplicate array1 */
      array2 = array1.Duplicate();
      ...
Allow the User to Append to a Table Field
A table field may or may not allow the user to add new rows at the bottom of the array. For more information about this, see How You Can Append Rows to the End of a Table Field.
How You Can Change Values in an Array
If an array is not displayed in a table field, the only way to change one of its values is by assignment. For example, the following statement updates the account balance of the customer in the fifth row:
custtable[5].acctbalance = custtable[5].acctbalance
    + credit - debit;
If your array's data type is a dynamically created user class, you must use a DynExpr to change values programmatically. For more information about changing values in dynamically created arrays, see Creating Dynamic Frames.
If an array is displayed in a table field whose bias allows data modification, the user can edit and change the displayed data. For a description of the biases for table fields, see Creating Dynamic Frames.
If two or more array variables point to the same data set and you change a value in the data set, the change is reflected in all the array variables that point to that data set. If any of the array variables is displayed in a table field, refresh the display to make the change visible if you want it to appear prior to the end of the event block. To refresh the display, use the UpdField method defined for the ActiveField system class.
The following statement updates display of the custtable table field:
field(custtable).UpdField();
How You Can Specify an Array Row to be Deleted
The SetRowDeleted method sets the _RowState attribute of a specified row to RS_DELETED. Marking a row deleted is useful to allow the application to access the data in the deleted row before the data is lost.
For example, assume a user edits the data displayed in a table field, changing data in some rows and deleting a few rows. When finished, the user clicks the Save button. The event block associated with this button checks the _RowState of each row in table field's array and, based on the row state, performs the appropriate operation in the database.
The following code fragment illustrates such row checking:
on click menu.save_menu =
begin
  i = custtable.FirstRow;
  while i <= custtable.LastRow do
      if custtable[i]._rowstate = RS_DELETED then
          repeated delete from customer
          where acctno = :cust_table[i].acctno;
      elseif custtable[i]._rowstate = RS_NEW then
          repeated insert into customer ...
      elseif custtable[i]._rowstate =
          RS_CHANGED then repeated update
          customer...
      endif;
      i = i + 1;
  endwhile;
  commit;
end;
Note:  If the user had actually removed rows from the array, rather than marking them as deleted, the application would not be able to apply those deletions to the database.
The syntax for the SetRowDeleted method is:
integer = ArrayObject.SetRowDeleted(rownumber = integer)
rownumber = integer
(Required.) Identifies the row to be deleted
The effect of marking a row as deleted depends on the initial state of the row:
Original row state of RS_CHANGED or RS_UNCHANGED
Marking the row as deleted gives the row a non-positive sequence number in the array but does not remove it from the array. The first row deleted becomes row 0, the next row deleted becomes -1, and so on. If, for example, 10 rows were marked RS_DELETED, the FirstRow method would return ‑9.
After a row is marked as deleted, OpenROAD moves the rows that follow the deleted row down in the array's row sequence. For example, if you mark row 3 deleted, then the current row 4 becomes row 3.
Original row state of RS_NEW or RS_UNDEFINED
Marking the row as deleted actually removes the row from the array.
In all cases, if the array is displayed in a table field, the row is removed from the table field display.
How You Can Delete Rows in an Array
When you want to delete rows from an array and do not need to keep track of the deleted rows, use either of the following methods:
The RemoveRow method to delete rows one row at a time
The syntax for the RemoveRow method is:
integer = ArrayObject.RemoveRow(rownumber = integer)
You must include the rownumber parameter to identify the row to be deleted.
If the row is deleted successfully, the method returns ER_OK. If the row is not found, the method returns ER_ROWNOTFOUND.
After the specified row is deleted, OpenROAD adjusts the numbers of the rows that followed the deleted row. For example, if you remove row number 5, then the current row 6 becomes the new row number 5.
The Clear method to delete all the rows at once
The syntax for the Clear method is:
integer = ArrayObject.Clear()
Both of these methods actually delete rows from the array (and any associated table field display), rather than just marking the rows deleted. Rows that have a _RowState value of RS_DELETED and those with positive sequence numbers are deleted.