A trigger is executed if the object it applies to is enabled (sensitive), and if the trigger is currently active and in scope.

The trigger is active if the statement that defines it has been executed. As discussed earlier in the book, the AVM processes statements in a procedure in the order it encounters them in. Thus, the definition of an object must come before the block of code that defines a trigger for the object. Otherwise, the AVM will not recognize the object reference. And the trigger definition must come before the user receives control and can perform the action that would fire the event and run the trigger.

The scope of a definitional trigger is the scope of the object it is defined for. The scope of a run-time trigger is the nearest containing procedure or trigger block where it is defined. So if a trigger definition is inside an internal procedure, then its scope is limited to that internal procedure. If it is outside of any internal procedure, its scope is the entire external procedure.

The AppBuilder places all trigger definitions toward the top of the procedure, following the Definitions section of the procedure but before the main block and all internal procedures. In this way the trigger blocks are scoped to the entire procedure and they are made active before any user actions that invoke them can occur.

To build a very simple example procedure to demonstrate some of the rules of run-time trigger processing in ABL:
  1. Define a button and give it an initial label, then define an INTEGER variable as a counter:
    DEFINE BUTTON bButton LABEL "Initial Label".
    DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
  2. Add a statement to enable the button in a frame. As you learned earlier, this causes both the button and its frame to be instantiated and realized:
    ENABLE bButton WITH FRAME fFrame.
  3. Define a run-time trigger for the button that changes its label so that you can see that the trigger fired:
    ON CHOOSE OF bButton IN FRAME fFrame
      DO:
        iCount = iCount + 1.
        bButton:LABEL IN FRAME fFrame = "External " + STRING(iCount).
      END.
  4. Create a WAIT-FOR statement that blocks the termination of the procedure and allows the user to click the button:
    WAIT-FOR CLOSE OF THIS-PROCEDURE.
  5. Run the procedure. As you would expect, the button’s label is changed when you click the button:

    The ON CHOOSE trigger you defined is scoped to the entire procedure and executes each time the button is pressed.

To define another trigger for the button in an internal procedure, make these changes to the procedure:

DEFINE BUTTON bButton LABEL "Initial Label".
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.

ENABLE bButton WITH FRAME fFrame.

ON CHOOSE OF bButton IN FRAME fFrame
  DO:
    iCount = iCount + 1.
    bButton:LABEL IN FRAME fFrame = "External " + STRING(iCount).
  END.
  
RUN ChooseProc.

WAIT-FOR CLOSE OF THIS-PROCEDURE.

PROCEDURE ChooseProc.
  ON CHOOSE OF bButton IN FRAME fFrame
    DO:
      iCount = iCount + 1.
      bButton:LABEL In FRAME fFrame = "Internal " + STRING(iCount).
    END.
END.

Before the WAIT-FOR statement the code runs the internal procedure ChooseProc. This procedure defines a different trigger for the same button, which identifies the trigger with the label Internal. The second trigger definition replaces the first one within the ChooseProc procedure.

Note that the scope of the button is the entire external procedure because it is defined at that level. The scope of the variable iCount is also the external procedure for the same reason. But what is the scope of the second trigger definition you just added?

It is scoped only to the internal procedure where it is defined. So what happens when you run this version of the procedure? Before you run it, walk through the code in your head to decide what happens.

The external procedure defines a trigger for the button. It then runs an internal procedure that defines a different trigger. That internal procedure then exits, without giving the user any chance to use the new trigger, and its trigger goes out of scope. So what happens when the user clicks the button? The AVM reverts to the original trigger.

Figure 1. Result of trigger example procedure

This example shows you that the AVM effectively keeps a stack of trigger definitions. If a later definition goes out of scope, the AVM reverts to the trigger definition that is now back in scope (if there is one).

So how would you see the effect of the internal procedure trigger? You can place a WAIT-FOR statement inside the internal procedure to force the AVM to wait until the user clicks the button.

To try this, add this statement to the end of the ChooseProc procedure:

WAIT-FOR CHOOSE OF bButton.

Note that you can wait for any event, not just the close of the procedure or its window. This statement waits until the user clicks the button exactly once. Then the WAIT-FOR is satisfied, the internal procedure exits, and the first trigger takes over.

To see the result of each button press, run the procedure again. This figure shows the result of the first four button presses.

Figure 2. Results of running the ChooseProc procedure