[Top of Document]

Appendix A: Disabling Forms' Default Navigation

Introduction

The code framework described in this appendix allows your users to run the application in a bitmapped environment using the mouse both for navigation and menu selection. It solves the problems encountered when migrating character-mode applications to a bitmapped environment. These problems were described in detail in the section "Disabling Default Navigation".

The Algorithm

This section gives a brief description of the algorithm. The user can click on anything that is visible. On receiving a mouse click, we will return to the previous (source) location and make use of the key-triggers in your application to navigate to the clicked (destination) location.

The algorithm will first attempt to navigate to the destination block. If it is successful, it will try to navigate to the destination record, followed by the destination item. If it is unsuccessful at any point, it will simply stop wherever the cursor is.

The algorithm will use the KEY-PREVIOUS-* triggers if the destination is the block or record or item that is immediately prior to the source. Otherwise, it will use the KEY-NEXT-* triggers to navigate to the destination. There are a couple of problems:

This algorithm also works in a multiple-form application. We will describe the algorithm in more detail in the next section.

Applying the Algorithm to Your Application

The following sections describe the steps needed to wrap the code framework around your application.

Parameters

Create the following parameters in your application.

 
 Parameter Name|Data Type|Maximum Length|Default Value|Comments

 currblk        character       50                     source block
 currrec        character        3                     source record
 curritem       character       50                     source item
 destblk        character       50                     destination block
 destrec        character        3                     destination record
 destitem       character       50                     destination item
 keyop          character        1                     = t when firing key-
                                                         triggers
                                                       = f  otherwise
 maxblk         number           5            20       maximum number of blocks
                                                         navigated to
 maxitem        number           5            30       maximum number of items
                                                         navigated to

The currblk, currrec and curritem parameters keep track of the source position and the destblk, destrec and destitem parameters track the destination position. maxblk and maxitem are the maximum number of blocks and items navigated to in the algorithm. keyop controls whether

You can customize the algorithm by changing the maxblk and maxitem on the command line when you run your application.

Form Level Triggers

Create the following form level triggers in your application. If you already have these form-level triggers in your application, simply add the code to your triggers.

PRE-FORM

   /* This trigger initializes the source location, a record group
      to store the row number for each block and turn on Forms' 
      default navigation.
   */
   declare
     rg recordgroup;
     blkname char(50);
     i number;
     blkcol groupcolumn;
     rowcol groupcolumn;
   begin
     /* initialize current source position */
     :parameter.currblk := get_form_property (:system.current_form, 
                                              first_navigation_block);
     :parameter.curritem := :parameter.currblk || '.' ||
                            get_block_property (:parameter.currblk, first_item);     :parameter.currrec := get_block_property (:parameter.currblk, 
   current_record);
   
     /* create record group to store current row position of each
        block. forms has the default functionality of remembering
        the row number of the blocks it has visited and goes back
        to that row during navigation. by disabling forms default
        navigation, we have to keep track of the row number for
        each visited block. each block is initialized at row 1.
     */
     rg := create_group ('blks');
     blkcol := add_group_column (rg, 'blk', char_column, 50);
     rowcol := add_group_column (rg, 'rownum', char_column, 3);
     i := 1;
     blkname := get_form_property (:system.current_form, 
                                   first_block);
     while blkname is not null loop
       add_group_row (rg, end_of_group);
       set_group_char_cell (blkcol, i, blkname);
       set_group_char_cell (rowcol, i, '1');
       blkname := get_block_property (blkname, nextblock);
       i := i + 1;
     end loop;
   
     /* set keyop so that any triggers that fire on startup,
        eg pre-block, when-new-item-instance, etc will be
        executed. it will be turned off in the form-level
        when-new-item-instance trigger.
     */
     :parameter.keyop := 't';
   end;

find_row is defined as follows. It searches the record group sequentially to find the row number associated with a block. You can replace the algorithm with a more efficient search algorithm if you have many blocks in your form.

 
   FUNCTION find_row (targetblk in char) RETURN number IS
     i number;
     blkcol groupcolumn;
     rg recordgroup;
     blkname char(50);
     totalrows number;
   BEGIN
       /* loop through the blocks stored in the record
          group sequentially to find targetblk and return the
          associated row number
       */
       rg := find_group ('blks');
       blkcol := find_column ('blks.blk');
       totalrows := get_group_row_count (rg);
       i := 1;
       blkname := get_group_char_cell (blkcol, 1);
       while targetblk <> blkname loop
         i := i + 1;
         blkname := get_group_char_cell (blkcol, i);
       end loop;
       return i;
   END;
  

WHEN-MOUSE-CLICK

   /* This trigger intercepts all mouse clicks and stores the
      click position in the DEST* parameters.  It also creates
      a timer so that it can return to the source location.
   */
   declare
     gotimer timer;
     i number;
   begin
     /* on mouse click, store destination position. */
     if :system.mouse_item is not null then
       :parameter.destitem := :system.mouse_item;
       :parameter.destblk := :system.current_block;
       :parameter.destrec := :system.mouse_record;
   
       /* since forms has the default functionality of going back
          to the row that it was in, and when it accepts the mouse
       :parameter.destitem := :system.mouse_item;
       :parameter.destblk := :system.current_block;
       :parameter.destrec := :system.mouse_record;
   
       /* since forms has the default functionality of going back
          to the row that it was in, and when it accepts the mouse
          click, it remembers the destination row rather than the
          source row.  Therefore, we have to find the source row
          in the record group and go back there so that forms
          remembers the original source row.
       */
       if :parameter.destblk <> :parameter.currblk then
         i := find_row (:parameter.destblk);
         go_record (to_number (get_group_char_cell ('blks.rownum', i)));
       end if;
   
       /* create a timer so that we can navigate back to the source
          position.
       */
       gotimer := create_timer('mytimer', 1, no_repeat);
     end if;
   end;

WHEN-TIMER-EXPIRED

   /* This trigger returns to the source location and attempts
      to navigate to the clicked location using the key
      triggers in your application.
   */
   declare
     rec_displayed number;
     i number;
     blk_nav_ok boolean := true;
     rec_nav_ok boolean := true;
     item_nav_ok boolean := true;
     required_flag char(6);
     updateable_flag char(6);
   begin
     /* first, return to the source position */
   
     /* if the destination item is required, we need to set updatable
        to false and required to true so that we can return to the
        source position.
     */
     required_flag := get_item_property(:parameter.destitem, required);
     updateable_flag := get_item_property(:parameter.destitem, updateable);
     if required_flag = 'TRUE' then
       set_item_property(:parameter.destitem, updateable, property_true);
       set_item_property(:parameter.destitem, required, property_false);
     end if;
   
     /* need go_block, go_record and go_item to return to source position.
        need go_record for the case when clicking in different item in the same
        multi-rec block. with go_item only, cursor will just go back to the item 
   in
        the same record. with go_record in place, need go_block for the case when 
        clicking in item in different block. otherwise cursor will just go back to 
        record in destination block with the go_record.
     */
   
     /* if the source and destination blocks are different, return to the
        source block.
     */
     if :parameter.currblk <> :parameter.destblk then
       go_block (:parameter.currblk);
   
       /* if cursor block is different than source block, navigation has 
          failed - do not continue with the algorithm.
       */
       if :system.cursor_block <> :parameter.currblk then
         blk_nav_ok := false;
       end if;
     end if;
   
     /* if block navigation succeeds, and source and destination records are
        different, return to the source record.
     */
     if blk_nav_ok and :parameter.currrec <> :parameter.destrec then
       go_record (:parameter.currrec);
       if :system.cursor_record <> :parameter.currrec then
   
         /* navigation has failed */
         rec_nav_ok := false;
       end if;
     end if;
   
     /* if block and record navigation succeed, return to the source item. */
     if blk_nav_ok and rec_nav_ok then
       go_item (:parameter.curritem);
   
       /* Set the updatable and required properties to their original values */
       if required_flag = 'TRUE' then
         set_item_property(:parameter.destitem, required, property_true);
         if updateable_flag = 'FALSE' then
           set_item_property(:parameter.destitem, updateable, property_false);
         end if;
       end if;
   
       /* Now, use key triggers to attempt to go to the clicked position */
   
       /* if destination block is the previous block, do the previous block key
          trigger.  Otherwise, do the next block key trigger until we reach the
          destination block or we hit the maxblk limit.
       */
   
       /* Doing previous block key trigger */
       if get_block_property (:parameter.currblk, previousblock) =
          :parameter.destblk then
         do_key('previous_block');
   
         /*  If current and cursor block are the same after the previous block
             key trigger, navigation has failed. Otherwise, update current 
             position.
         */
         if :parameter.currblk = :system.cursor_block then
           blk_nav_ok := false;
         else
           :parameter.currblk := :system.cursor_block;
           :parameter.currrec := :system.cursor_record;
           :parameter.curritem := :system.cursor_item;
         end if;
       else
   
         /* Doing next block key trigger */
   
         /* maxblk is needed to prevent an infinite loop because 
            the key triggers may not be able to bring the cursor
            to the destination position
         */
         i := 1;
         while blk_nav_ok and i <= :parameter.maxblk and 
               :parameter.currblk <> :parameter.destblk loop
               :parameter.currblk <> :parameter.destblk loop
           do_key('next_block');
           if :parameter.currblk = :system.cursor_block then
             blk_nav_ok := false;
           else
             :parameter.currblk := :system.cursor_block;
             :parameter.currrec := :system.cursor_record;
             :parameter.curritem := :system.cursor_item;
             i := i + 1;
           end if;
         end loop;
       end if;
   
       /* if block navigation succeeds, go to the destination row using the
          same technique as above.  We can rely on the number of records 
          displayed as our limit.
       */
       if blk_nav_ok then
         i := 1;
   
         rec_displayed := get_block_property(:parameter.destblk,
                          records_displayed);
         while i < rec_displayed and :parameter.currrec <> :parameter.destrec loop
           if to_number(:parameter.destrec) < to_number(:parameter.currrec) then             if :parameter.destitem = :parameter.curritem then
   
               /* user has clicked in the same item in a different row */
               do_key ('up');
             else
   
               /* user has clicked in an item different than source
                  in a different row
               */
               do_key ('previous_record');
             end if;
           else 
             if :parameter.destitem = :parameter.curritem then
               do_key ('down');
             else
               do_key('next_record');
             end if;
           end if;
   
         /*  If current and cursor record are the same after the 
             key trigger, navigation has failed. Otherwise, update
             current position.
         */
           if :parameter.currrec = :system.cursor_record then
             rec_nav_ok := false;
           else
             :parameter.currrec := :system.cursor_record;
             :parameter.curritem := :system.cursor_item;
             i := i + 1;
           end if;
         end loop;
   
         /* if record navigation succeeds, go to the destination item using the
            same technique as above.  Here, we rely on maxitem as our limit.
         */
         if rec_nav_ok then
           if :parameter.currblk||'.'||
              get_item_property (:parameter.curritem, previousitem) = 
              :parameter.destitem then
             do_key('previous_item');
             if :parameter.curritem = :system.cursor_item then
               item_nav_ok := false;
             else
               :parameter.curritem := :system.cursor_item;
             end if;
           else
             i := 1;
             while item_nav_ok and i<:parameter.maxitem and
                   :parameter.curritem <> :parameter.destitem loop
               do_key('next_item');
               if :parameter.curritem = :system.cursor_item then
                 item_nav_ok := false;
               else
                 :parameter.curritem := :system.cursor_item;
                 i := i + 1;
               end if;
             end loop;
           end if;
         end if;
       end if;
     end if;
   end;

WHEN-NEW-ITEM-INSTANCE

   /* after successful navigation to the destination, update 
      the row number in the record group and current position.
      at this point, turn Forms' default navigation off.
   */
   declare
     i number;
   begin
     if :parameter.keyop = 't' then
       :parameter.currrec := :system.cursor_record;
       i := find_row (:parameter.currblk);
       set_group_char_cell ('blks.rownum', i, :parameter.currrec);
   
       :parameter.currblk := :system.cursor_block;
       :parameter.curritem := :system.cursor_item;
       :parameter.keyop := 'f';
     end if;
   end;
  

ON-FETCH and KEY-triggers

In order to turn off Forms' default navigation, we need to wrap some code around your triggers. You should put the following if-statement around your code in the triggers listed in the following figure.

   
   if doit then
   ...
   <your code in the triggers listed below>
   ...
   end if;
   
         PRE-BLOCK                POST-BLOCK 
         PRE-RECORD               POST-RECORD
         PRE-TEXT-ITEM            POST-TEXT-ITEM 
         WHEN-NEW-ITEM-INSTANCE

Figure A-1 Triggers to be modified doit is a function as shown below. It simply returns whether the value of the parameter keyop is true.

 
   FUNCTION doit RETURN boolean IS
   BEGIN
     return (:parameter.keyop = 't');
   END;
  

Since keyop would be false (f) all the time, the code in these triggers will not be executed. We need to turn keyop on (t) using some form-level key-triggers and the ON-FETCH trigger. Create the form-level triggers shown in the figure below with the following line of code. If the application has these triggers, simply add the code at the end of your triggers.

 
   :parameter.keyop := 't';

              KEY-NXTBLK            KEY-PRVBLK
              KEY-NXTREC            KEY-PRVREC
              KEY-NEXT-ITEM         KEY-PREV-ITEM
              KEY_DOWN              KEY_UP
              ON-FETCH
 

Figure A-2 Form-level Triggers The ON-FETCH trigger is required so that the code in the PRE-triggers will be executed when the user executes a query using the menu.

Since the triggers in Figure A-2 are defined at form-level, there are a couple of problems.

You can then make all your blocks inherit from this property class. If these triggers are already defined for any block, record or item, your trigger will override those defined in the property class.

Query Operations

Since keyop is always false unless it is a key operation or a mouse click, if your query-related triggers use any restricted built-ins, navigational code will not be executed because we have wrapped the if-statement around all the triggers listed in Figure A-1. In this case, you need to set keyop to true in these triggers. You have to create a procedure to set the keyop parameter to true as follows.

 
   PROCEDURE setit IS
   BEGIN
     :parameter.keyop := 't';
   END;
  

There are only a few places that you need this. You have to call setit at the beginning of the ON-CLEAR-DETAILS and ON-POPULATE-DETAILS triggers and any forms-generated CLEAR-***-DETAILS procedures if they use restricted built-ins.

Continue

Part #: A32362


Copyright © 1996 Oracle Corporation. All Rights Reserved.