Ask Reuben

Set Current Row To First Sort Row

How can I set the current row to be the first sorted row?

When the front-end applies a sort to the rows in a table, a question that then gets asked is how can I change the current row so that it is the first sorted row.

Before answering the question I will provide some background.  In the 4gl there will be an underlying array variable.  When the front-end applies a sort (clicking in the table column header), this does not change the values in the array nor does it change the pointer to the current row.  array_variable[i].* will still hold the same values, and arr_curr() will still return the same value.

With the example using the code at the end of this article, we can illustrate this.  I have coded it so that if sorting on the Date column, rows 3 and 7 will be our first and last row, and row 1 will always be somewhere in the middle.  So we start like this with the rows being displayed in the same order as they are in the 4gl array,  I then click on the column header for the Date column and the table is now sorted by the Date column.  Row 1, the current row is now in the middle somewhere, if we click the Row Details we see that arr_curr(), the Current Row still remains 1 …

BeforeAfter

… now a question that gets asked is how can we make the current row the top row.

First of all, if you press the HOME key, this will make the top row the current row.

On touch devices or with a mouse you can tap/click on the top row to make it the current row, this may require some scrolling as well in order to see the top row before you tap/click it.

Alternatively we could add some code like in the ON ACTION gototop in that sample code.  The key is that it uses the visualToArrayIndex method to find what the row index for the top row is, and then calls setCurrentRow to set the current row to that row.  If you do that and again click the Row Details button, we see that scr_line() returns 1 being the top row, and arr_curr() returns a value other than 1, being the position of the row in the 4gl array.  Behind the scenes, the 4gl array remains unchanged.

BeforeAfter

Now exit the program and start it again.  With stored settings, the table remembers to sort by the Date Column.  The current row is still 1, and so the current row is in the middle of the array.  If you uncomment the ON SORT block and repeat …

ON SORT
    IF first THEN
        CALL DIALOG.setCurrentRow("scr", DIALOG.visualToArrayIndex("scr", 1))
        LET first = FALSE
    END IF

… this is one technique you can use to position the current row at the top of the array when an array is sorted by stored settings.  The use of the IF first means this code is only executed when the dialog starts.  You could remove the IF first and have it always set the current row to the top row after a sort.

There is an fglprofile entry I should also mention at this point.  The Dialog.currentRowVisibleAfterSort entry will change the offset (move the scrollbars) so that the current row is always visible after a sort.   It does not make the current row the top row.  You see the benefit of this with large arrays where a sort is likely to move the current row outside the currently visual area.  It typically makes sense to move the scrollbars so that the current row remains visible rather than having a table display and no current row be visible and you having to scroll up/down to find it.

Consideration has been given to adding adding another fglprofile to change the current row to be the new top row but there are some complications.  Should we process the AFTER ROW/BEFORE ROW that would occur if user moves to the top row themselves.  An extra interchange would have to occur with the front-end to determine the sort column.  (you can see this by putting the CALL DIALOG.setCurrentRow("scr", DIALOG.visualToArrayIndex("scr", 1)) in a BEFORE DISPLAY.  At the time of the BEFORE DISPLAY, the sort has not been done and so visualToArrayIndex  does not return the new values.

IMPORT util
MAIN
    DEFINE arr DYNAMIC ARRAY OF RECORD
        field1 INTEGER,
        field2 STRING,
        field3 DATE
    END RECORD
    DEFINE i INTEGER
    DEFINE first BOOLEAN

    WHENEVER ANY ERROR STOP
    DEFER INTERRUPT
    DEFER QUIT
    OPTIONS FIELD ORDER FORM
    OPTIONS INPUT WRAP

    CALL ui.Interface.loadStyles( base.Application.getArgument(0))
    CLOSE WINDOW SCREEN

    FOR i = 1 TO 10
        LET arr[i].field1 = i
        LET arr[i].field2 = ASCII (64 + i), ASCII (96 + i), ASCII (96 + i)
        LET arr[i].field3 = TODAY - (1 + util.Math.rand(100))
    END FOR
    -- Set row 10 and 20 ot be our end rows, and oprevent row 1 being top/bottom by chance
    LET arr[3].field3 = TODAY
    LET arr[7].field3 = TODAY - 102

    OPEN WINDOW w WITH FORM base.Application.getArgument(0) ATTRIBUTES(TEXT = "Example")
    DISPLAY ARRAY arr TO scr.* ATTRIBUTES(UNBUFFERED)
        BEFORE DISPLAY
            LET first = TRUE

        ON ACTION gototop ATTRIBUTES(TEXT = "Home")
            CALL DIALOG.setCurrentRow("scr", DIALOG.visualToArrayIndex("scr", 1))

        ON ACTION display_current_row ATTRIBUTES(TEXT = "Row Details")
            MESSAGE SFMT("Current row is %1, Screen Line is %2", arr_curr(), scr_line())

            --ON SORT
            --    IF first THEN
            --        CALL DIALOG.setCurrentRow("scr", DIALOG.visualToArrayIndex("scr", 1))
            --        LET first = FALSE
            --    END IF
    END DISPLAY
END MAIN
LAYOUT
TABLE
{
[f01      ][f02      ][f03      ]
}
END
END
ATTRIBUTES
EDIT f01 = formonly.field1, TITLE="Code";
EDIT f02 = formonly.field2, TITLE="Name";
DATEEDIT f03 = formonly.field3, TITLE="Date";
INSTRUCTIONS
SCREEN RECORD scr(field1, field2, field3)
<?xml version="1.0" encoding="ANSI_X3.4-1968"?>
<StyleList>
    <Style name="Window">
        <StyleAttribute name="windowType" value="normal"/>
    </Style>
    <Style name="Table">
        <StyleAttribute name="headerAlignment" value="auto"/>
    </Style>
</StyleList>