Controlling Window, Block, and Region Behavior

Controlling Window Behavior

Controlling window behavior includes coding logic that positions windows upon opening, controlling which windows close under various conditions, providing context-sensitive titles for detail windows, and so on. If you have master-detail relationships between blocks in separate windows, you must also code logic for that situation.

See: Coding Master-Detail Relations

Positioning Windows Upon Opening

Example

The Purchase Order header window contains a button labeled "Lines" that leads to the LINES block in a different window.

  1. Add or modify the following triggers:

    • Trigger: PRE-FORM:

      app_window.set_window_position('HEADER','FIRST_WINDOW');
    • Trigger: WHEN-BUTTON-PRESSED on the LINES button:

      app_custom.open_window('LINES');
      
  2. Modify APP_CUSTOM.OPEN_WINDOW as follows:

     IF wnd = 'LINES' THEN
       APP_WINDOW.SET_WINDOW_POSITION('LINES',
                           'CASCADE','HEADER');
       go_block('LINES');
    END IF;
    

    The styles available are:

    • CASCADE: Child window overlaps the parent window, offset to the right and down by 0.3" from the current position of the parent window. Usually used for detail windows.

    • RIGHT, BELOW: Child window opens to the right of, or below, the parent window without obscuring it.

    • OVERLAP: Detail window overlaps the parent window, aligned with its left edge, but offset down by 0.3".

    • CENTER: Window opens centered relative to another window. Usually used for modal windows.

    • FIRST_WINDOW: Position the window immediately below the toolbar. Usually used for the main entity window.

Closing Windows

The window close events for all non-modal windows (but no modal windows) get passed to APP_CUSTOM.CLOSE_WINDOW. The default code provided in the TEMPLATE form does the following:

You need to modify this procedure to account for any other behaviors you require. Specifically, modify it to handle block coordination issues and detail windows.

Remember that you must move the cursor out of the window before closing it, otherwise the window reopens automatically.

To close the first window of a form, which is equivalent to "File->Close Form" call APP_WINDOW.CLOSE_FIRST_WINDOW.

Example

In a form with windows "Header," "Lines," and "Shipments," where Lines is a detail of Header, and Shipments is a detail of Lines, the logic to close the windows is as follows:

 PROCEDURE close_window (wnd VARCHAR2) IS
   IF wnd = 'HEADER' THEN
      -- 
      -- Exit the form
      --
      app_window.close_first_window;
   ELSIF wnd = 'LINES' THEN
      --
      -- Close detail windows (Shipments)
      --
      app_custom.close_window('SHIPMENTS');
      --
      -- If cursor is in this window,
      -- move it to the HEADER block
      --
      IF (wnd = GET_VIEW_PROPERTY(GET_ITEM_PROPERTY(
          :SYSTEM.CURSOR_ITEM,ITEM_CANVAS), 
          WINDOW_NAME)) THEN
         GO_BLOCK('HEADER');
      END IF;
   ELSIF wnd = 'SHIPMENTS' THEN
      --
      -- If cursor is in this window,
      -- move it to the LINES block
      --
      IF (wnd = GET_VIEW_PROPERTY(GET_ITEM_PROPERTY(
           :SYSTEM.CURSOR_ITEM, ITEM_CANVAS), 
           WINDOW_NAME)) THEN
         GO_BLOCK('LINES');
      END IF;
   END IF;
   --
   -- THIS CODE MUST REMAIN HERE.  It ensures  
   -- the cursor is not in the window that will 
   -- be closed by moving it to the previous block.
   --
   IF (wnd = GET_VIEW_PROPERTY(GET_ITEM_PROPERTY(
        :SYSTEM.CURSOR_ITEM, ITEM_CANVAS), 

            WINDOW_NAME)) THEN
      DO_KEY('PREVIOUS_BLOCK');
   END IF;
   --
   -- Now actually close the designated window
   --
   HIDE_WINDOW(wnd);  
END close_window;

Warning: You must leave the default clause that attempts to move the cursor and close the window name passed to this procedure.

See: Coding Master-Detail Relations

Setting Window Titles Dynamically

Warning: Do not include parentheses or colons (the characters " ( " or " : ") in any of your window titles. These characters get added by the APPCORE window titling routine when you need to dynamically change the title to show context. Your base window titles should never include these characters. If you use a hyphen ( - ), do not surround it with spaces. In other words, do not both precede and follow your hyphen with spaces.

Dynamic Title Example

In the Enter Journal form, show the current Set of Books and Journal name in the Journal Lines window.

  1. Set the Lines window title to "Journal Lines" in the Oracle Forms Developer.

  2. Code the PRE-RECORD trigger of the Journal block:

    app_window.set_title('LINES', name_in('Journal.SOB'),
     :journal.name);
    
  3. Code the WHEN-VALIDATE-ITEM trigger of the journal.names field:

    app_window.set_title('LINES', name_in('Journal.SOB'),
     :journal.name);
  4. If you need to change the base title of a window, call SET_WINDOW_ PROPERTY(...TITLE...). Any future calls to APP_WINDOW.SET_ TITLE preserve your new base title.

Controlling Block Behavior

Controlling block behavior includes coding master-detail relations and implementing a combination block.

See: Coding Master-Detail Relations and Implementing a Combination Block.

Coding Master-Detail Relations

When a detail block is in a different window than its master, and each window is non-modal, then the detail block must provide a mechanism for the user to toggle between immediate and deferred coordination. This allows a user to keep a block visible, but control the performance cost of coordinating detail records when the master record is changed.

When a detail block is not visible, its coordination should always be deferred. Use the procedure APP_WINDOW.SET_COORDINATION to coordinate master-detail blocks in different windows.

See: APP_WINDOW: Window Utilities

The sample code below uses the following objects:

Coordination Between Windows

  1. Create a button to navigate to the detail block.

  2. Create a coordination check box in a control block in the detail window to specify the user's preference of immediate or deferred coordination when the window is open. The check box should have the CHECKBOX_COORDINATION property class, which provides a value of "IMMEDIATE" when checked and "DEFERRED" when unchecked. The check box value should default to checked (IMMEDIATE).

  3. Create your item handler procedures as follows:

    PACKAGE BODY control IS
         PROCEDURE lines(EVENT VARCHAR2) IS
         BEGIN
            IF (EVENT = 'WHEN-BUTTON-PRESSED') THEN
               app_custom.open_window('LINES');
            END IF;
         END lines;
    
         PROCEDURE orders_lines(EVENT VARCHAR2) IS
         BEGIN
            IF (EVENT = 'WHEN-CHECKBOX-CHANGED') THEN
               APP_WINDOW.SET_COORDINATION(EVENT,
                                        :control.orders_lines,
                                        'ORDERS_LINES');
            END IF;
          END orders_lines;
    END control;
    
  4. Customize the APP_CUSTOM template package as follows:

    In the OPEN_WINDOW procedure, add:

     IF (WND = 'LINES') THEN
            APP_WINDOW.SET_COORDINATION('OPEN-WINDOW',
                                       :control.orders_lines,
                                       'ORDERS_LINES');
            GO_BLOCK('LINES');
         END IF;
    

    In the CLOSE_WINDOW procedure, add:

     IF (WND = 'LINES') THEN
           APP_WINDOW.SET_COORDINATION('WHEN-WINDOW-CLOSED',
                                       :control.orders_lines,
                                       'ORDERS_LINES');
         END IF;
    
  5. Call your field and event handler procedures in:

    Trigger: WHEN-BUTTON-PRESSED on control.lines:

    control.lines('WHEN-BUTTON-PRESSED');

    Trigger: KEY-NXTBLK on ORDER:

    control.lines('WHEN-BUTTON-PRESSED');

    Trigger: WHEN-CHECKBOX-CHANGED on control.order_lines:

    control.orders_lines('WHEN-CHECKBOX-CHANGED');

Implementing a Combination Block

Each item in a block can have its own Number of Items Displayed property, so you can have a single block in which some items are single-record (Detail) and some are multi-record (Summary). When you implement a combination block, most items appear twice, so coordination of the values in these items must be managed. The Synchronize with Item property does this automatically. You control which portion of the block is navigated to in different situations using a field called Switcher. The Switcher field is the first navigable item in the block. When the cursor enters the Switcher, it is immediately moved to the first item in either the Detail or Summary portion of the block.

  1. Setting up the combination block

    Create two windows and canvases to hold the different portions of your block. Use the non-mirror items in your block for the Summary portion. Duplicate the items to create the Detail portion. The Detail portion of your combination block should be sequenced first. Thus, when the user does not fill in a required item and tries to commit the block, Oracle Forms positions the cursor in that item in the Detail block.

  2. Setting the item properties

    For the mirror items, change the item names to reflect the real item that they are mirroring (for example, name the mirror item of "status" to "status_mir"). Set the Synchronize with Item property, and make sure the Database Item property is set to Yes (if the synchronized items are a base table item).

    Set the block-level Number of Records Displayed property for your Summary portion. This will get picked up by the items so long as you do not explicitly set the Number of Items Displayed property. So that your Detail portion items do not get the same value, explicitly set their Number of Items Displayed property to 1.

    To prevent the user from tabbing out of the Detail and into the Summary, set the Previous Navigation Item property for the first Detail item, and the Next Navigation Item property for the last Detail item.

    To enforce the standard multi-record block navigation behavior of Change Record, call APP_COMBO.KEY_PREV_ITEM in the KEY-PREV-ITEM (Fire in ENTER-QUERY mode: No) trigger of the first navigable item of the Summary portion, and call next_record in the KEY-NEXT-ITEM trigger (Fire in ENTER-QUERY mode: No) of the last navigable item of the Summary portion.

    If you are converting an existing block into a combination block, do not forget to change references in any existing triggers to recognize that there are now two instances of every field.

    See: APP_COMBO: Combination Block API.

  3. The Drilldown Record Indicator

    Add a Drilldown Record Indicator that does an execute_trigger('SUMMARY_DETAIL').

  4. The Record Count Parameter

    Create a parameter to store the record count for the portion of the block you are currently in. Name the parameter <block>_RECORD_COUNT, where <block> is the name of the combination block. The APPCORE code depends on this naming standard. This information is used to determine which portion of the block to navigate to. The parameter should have a Data Type of NUMBER and a default value of 2, so that the cursor is initially in the Summary portion. (If you want the cursor to start in the Detail portion, set the default value to 1).

    Create a block level WHEN-NEW-ITEM-INSTANCE trigger (Execution Hierarchy: Before) that contains the following code:

     :PARAMETER.<block>_RECORD_COUNT := 
    GET_ITEM_PROPERTY(:SYSTEM.CURSOR_ITEM,
                             RECORDS_DISPLAYED);
    
  5. The Switcher

    Create a text item and assign it the property class SWITCHER. It needs to be the lowest sequenced item in the block. Place it at (0,0) on the toolbar canvas (the switcher belongs on the toolbar canvas because whatever canvas it is on paints). Create an item-level WHEN-NEW-ITEM-INSTANCE trigger (Execution Hierarchy: Override) that contains the following code:

     IF(:PARAMETER.<block>_RECORD_COUNT > 1) THEN
      GO_ITEM('<first Summary field>');
    ELSE
      APP_WINDOW.SET_WINDOW_POSITION('<Detail window>',
             'OVERLAP',
     '<Summary window>');
      GO_ITEM('<first Detail field>');
    END IF;
    
  6. The Summary/Detail Menu Item

    Create a block-level SUMMARY_DETAIL trigger (Execution Hierarchy: Override) that contains the following code:

     IF GET_ITEM_PROPERTY(:SYSTEM.CURSOR_ITEM,
                         RECORDS_DISPLAYED) > 1 THEN
       :PARAMETER.<block>_RECORD_COUNT := 1;
    ELSE
       :PARAMETER.<block>_RECORD_COUNT := 2;
    END IF;
    GO_ITEM('<block>.Switcher');
    

    This code changes the value in the RECORDS_DISPLAYED parameter so that the Switcher sends the cursor into the opposite portion of the block. It will fire whenever the user chooses "Go -> Summary/Detail."

    Create a block-level PRE-BLOCK trigger (Execution Hierarchy: Override) that contains the following code:

     APP_SPECIAL.ENABLE('SUMMARY_DETAIL', PROPERTY_ON);
    

    Finally, create a form-level PRE-BLOCK trigger (Execution Hierarchy: Override) that contains the code:

     APP_SPECIAL.ENABLE('SUMMARY_DETAIL', PROPERTY_OFF);
    

    If all blocks are combination blocks, you can turn on SUMMARY_DETAIL at the form-level and ignore the PRE-BLOCK trigger. If most blocks are combination blocks, you can turn SUMMARY_DETAIL on at the form-level, and disable it at the block-level for those blocks that are not combination blocks.

  7. Initial navigation and window operations

    If your combination block is the first block in the form, position the two windows in the PRE-FORM trigger with the following calls:

     APP_WINDOW.SET_WINDOW_POSITION('<Summary window>',
                                       'FIRST_WINDOW');
        APP_WINDOW.SET_WINDOW_POSITION('<Detail window>',
                                       'OVERLAP',
                                       '<Summary window>');
    

    Usually, the Summary is entered first, but there are cases where it is dynamically determined that the Detail should be entered first. If you need to dynamically decide this, set the parameter <block>_RECORD_COUNT in the PRE-FORM trigger (1 to send it to the Detail, 2 to send it to the Summary).

Coding Tabbed Regions

Definitions

Tabbed Region

A tabbed region is the area of the window that contains a group of related tabs. The group of related tabs and their corresponding tab pages are considered to make up the tabbed region. In Forms Developer, this is called a tab canvas. Each tab canvas consists of one or more tab pages.

Tab Page

A tab page is the area of a window and related group of fields (items) that appears when a user clicks on a particular "tab" graphic element. The term "tab" is often used interchangeably with the term "tab page". In Form Builder, a tab page is the surface you draw on. Form Builder sizes it automatically within the tab canvas viewport.

Topmost Tab Page

The topmost tab page is the tab page that is currently "on top"; that is, the currently-selected and displayed tab page.

Fixed Fields

Fixed fields are fields or items that appear in several or all tab pages of a particular tabbed region. Fixed fields may include context fields and/or primary key fields, the block scrollbar, a current record indicator or drilldown indicator, and descriptive flexfields.

Alternative Region Fields

Alternative region fields (unique tab page fields) are fields that are unique to a particular tab page and therefore do not appear on other tab pages of a particular tabbed region. Alternative region fields are the opposite of fixed fields, which appear on several or all tab pages of a particular tabbed region.

Controls

"Controls" is another term for fields, items, or widgets. Includes text items, display items, check boxes, scroll bars, buttons, tabs, option groups, and so on.

Tabbed Region Behavior

The desired Oracle E-Business Suite behavior for tabbed regions is to show the tab page and move focus to the appropriate field depending on which tab is clicked. You must write code to achieve this behavior, because the standard behavior in Oracle Forms is to put the focus in the tab widget itself when the user clicks on a tab.

In Oracle Forms, "cursor focus" is the same thing as "mouse focus," thus the term is simply "focus."

Keyboard-only Operation

Users can access a tab directly via the keyboard using a definable hot key to access a list of available tabs (the [F2] key by default).

In addition, as the user presses Next Field or Previous Field, navigation cycles through all the fields of the block, and across tab pages as appropriate. The selected tab must always be synchronized with the current group of fields that is being displayed. Because many tabbed regions use stacked canvases to hold the fields, rather than placing the fields directly on tab pages, the code needs to keep the tabs synchronized with the stacked canvases.

Dynamic Tab Layouts

Hide a tab at startup if it will not apply for the duration of the form session. Do not hide and show tabs within a form session. It is acceptable, though not ideal, to have only one tab remaining visible. Dynamically disable and enable a tab if its state is determined by data within each record.

Other Behaviors

Tabs should operate in enter-query mode. The field that a go_item call goes to in enter-query mode must be queryable. Some forms also require canvas scrolling within a tab page.

These desired behaviors result in the specific ways of coding handlers for tabbed regions described in the following sections.

Three Degrees of Coding Difficulty

The three degrees of difficulty require different types of layout methods and coding methods.

The layout method differences include using stacked canvases or not, and how many of them. The coding method differences include extra code that is required for handling the behavior of tabs with stacked canvases.

Simple case: no scrolling or fixed fields

The simple case includes single-row tab pages where no fields are repeated on different pages. These are typically separate blocks of the form.

If you have a form with multiple separate multi-row blocks represented as one tabbed region (one block per tab page, and separate block scrollbars for each, but no horizontal scrolling of fields), that can also be coded as the simple case. For example, the Users window on the System Administration responsibility fits the simple case.

In the simple case, you place items directly onto the tab pages. The simple case does not require any stacked canvases.

Medium case: scrolling but no fixed fields

The medium case covers single-row tab pages where no fields are repeated on different pages, but scrollbars are required to allow access to all fields within a tab page. These tab pages are typically each separate blocks of the form.

If you have a form with multiple separate multi-row blocks represented as one tabbed region (one block per tab page, separate block scrollbars for each, and horizontal scrolling of fields), that can also be coded as the medium case. "Fixed" (but not shared) objects such as block scrollbars and buttons can be placed directly on the tab page in this case.

In the medium case, you place items onto stacked canvases, in front of the tab pages, to facilitate scrolling of fields.

Difficult case: fixed fields with or without scrolling

The difficult case covers the presence of fixed fields shared across different tab pages. This case includes any multi-row blocks spread across multiple tab pages. Fixed fields usually include context fields, current or drilldown record indicator, descriptive flexfields, and the block scrollbar.

For the fixed field case, you place items onto stacked canvases, in front of the tab pages, to facilitate scrolling of fields. An extra stacked canvas is required for the fixed fields, and additional code is required in the tab handler.

Implementing Tabbed Regions

Implementing tabbed regions essentially consists of two main phases:

The following procedures describe how to implement tabbed regions to follow Oracle E-Business Suite standards. These steps apply to all three cases (simple, medium, difficult), with any differences noted in the step description.

See: Creating the Layout in Forms Developer

Coding Your Tab Handler

Creating the Layout in Forms Developer

This procedure describes how to create the layout in Forms Developer.

  1. Create the tab canvas. Name the tab canvas following the standard TAB_ENTITY_REGIONS (where ENTITY is your entity such as LINES) or similar. For example, the tab canvas name could be something like TAB_LINES_REGIONS. Apply the TAB_CANVAS property class.

    Set the Window property of the tab canvas so the tab canvas appears in the correct window. If you do not set the Window property of the tab canvas to be the correct window, you will not be able to use the View -> Stacked Views menu choice in Form Builder to display your tab canvas on the content canvas.

  2. Adjust the tab canvas. Sequence the canvas after the content canvas, and before any stacked canvases that will appear in front of it. Adjust its viewport in the Layout Editor. Show the content canvas at the same time so you can position the tab canvas well.

  3. Create the tab pages.

    For the medium and difficult cases, the names of the tab pages must match the names of the "alternative region" stacked canvases they correspond to.

  4. Adjust the tab pages. Apply the property class TAB_PAGE to each tab page. Set the tab page labels. Sequence your tab pages in the Object Navigator to match your item tabbing sequence.

  5. For the difficult case only, create the fixed field stacked canvas. Name it (tab_canvas)_FIXED. Sequence it after the tab canvas but before any "alternative region" stacked canvases that you will create for the difficult case. Apply the property class CANVAS_STACKED_FIXED_FIELD. Set the fixed field canvas viewport just inside the tab canvas viewport.

  6. For the medium and difficult cases only, create the "alternative region" stacked canvases. These canvases must all have the same viewport size and position. Check the Visible property for your alternative region stacked canvases; only the first one to be displayed should be set to Yes.

    For the difficult case, these "alternative region" canvases will obscure part, but not all, of the fixed field canvas. Make sure their viewport positions and sizes land in the appropriate place relative to the fixed field canvas.

  7. Place your items on the appropriate tab pages or stacked canvases. Position the block scrollbar, if any, on the right edge of the canvas.

    If you are using stacked canvases, be sure that the stacked canvases do not overlap fields that are placed directly on the tab pages. Similarly, make sure that any ”alternative region” stacked canvases do not overlap any items on the fixed field stacked canvas.

  8. Adjust your layout. Set the field prompts as properties of your fields as appropriate.

    Note on arranging your tabbed region layout: the primary standard for arranging the layout of fields and other elements in your tabbed region is to create an aesthetically pleasing appearance. This includes leaving sufficient space around the inside and outside of the actual tab pages so that the layout does not appear overly crowded. There is no single set of required layout settings to achieve this goal. For example, a multi–row check box at the end of a tabbed region may require more white space between it and the edge of the tab page than is needed to make a text item look good in the same position.

    Note also that the Forms Developer Layout Editor does not render tabs and stacked canvases with the Oracle Look and Feel. You will see the Oracle Look and Feel only at runtime. You need to rely on the numeric value of viewports, rather than what you see at design time.

    For more information, see the Oracle E-Business Suite User Interface Standards for Forms-Based Products.

Coding Your Tab Handler

This procedure describes the second phase of implementing tabbed regions, coding the tab handler.

  1. Code your tab handler. Oracle provides two template files to make writing the handler easy:

    • FNDTABS.txt for the simple and medium cases

    • FNDTABFF.txt for the fixed field case

    The location of FNDTABS.txt and FNDTABFF.txt is under FND_TOP/resource (the file names may be lowercase). Choose the appropriate tab handler template file (FNDTABS.txt or FNDTABFF.txt). Import the handler text into your form (typically in the block package or the control package) or library and modify it to fit your form. Modify it as appropriate to fit your form object names and to account for any special behavior required. The file includes extensive comments that help you modify the correct parts of the file.

  2. Call your tab handler from triggers. Add a form-level WHEN-TAB-PAGE-CHANGED trigger and make it call your new handler. The trigger should pass the WHEN-TAB-PAGE-CHANGED event to the handler. For example:

    MY_PACKAGE.TAB_MY_ENTITY_REGIONS('WHEN-TAB-PAGE-CHANGED');
    

    Code the WHEN–NEW–ITEM–INSTANCE trigger to call your new handler. You typically code this trigger at the block level (Execution Hierarchy Style: Before). For example:

    MY_PACKAGE.TAB_MY_ENTITY_REGIONS('WHEN-NEW-ITEM-INSTANCE');
    

Tab Handler Logic

Your tab handler typically accepts calls from the following triggers (events):

The tab handler has a branch for each of these events.

WHEN-TAB-PAGE-CHANGED Logic

When the user presses a tab, your WHEN-TAB-PAGE-CHANGED logic:

The WHEN-TAB-PAGE-CHANGED trigger fires only when user clicks on a tab. It cannot be fired programmatically, and it can only exist at the form level.

Text of FNDTABS.txt WHEN-TAB-PAGE-CHANGED Branch

Here is the WHEN-TAB-PAGE-CHANGED branch of FNDTABS.txt file (simple and medium cases):

IF (event = 'WHEN-TAB-PAGE-CHANGED') THEN
    if name_in('system.cursor_block') = 'MY_BLOCKNAME' then 
       validate(item_scope);
       if not form_success then
           -- Revert tab to prior value and exit
           set_canvas_property('TAB_ENTITY_REGIONS',
                       topmost_tab_page, 
                       name_in('system.tab_previous_page'));
           return;
       end if;
            -- Move to first item on each tab
       if target_canvas_name = 'MY_FIRST_TAB_PAGE' then
         go_item('MY_BLOCKNAME.FIRST_TAB_PAGE_FIRST_FIELD');
       elsif target_canvas_name = 'MY_SECOND_TAB_PAGE' then
        go_item('MY_BLOCKNAME.SECOND_TAB_PAGE_FIRST_FIELD');
       elsif target_canvas_name = 'MY_THIRD_TAB_PAGE' then
         go_item('MY_BLOCKNAME.THIRD_TAB_PAGE_FIRST_FIELD');
       end if;
    else
       show_view(target_canvas_name);
    end if;

Text of FNDTABFF.txt WHEN-TAB-PAGE-CHANGED Branch

Here is the WHEN-TAB-PAGE-CHANGED branch of FNDTABFF.txt file (fixed field case):

IF (event = 'WHEN-TAB-PAGE-CHANGED') THEN
         if name_in('system.cursor_block') = 'MY_BLOCKNAME' then 
           -- Process the 'First' tab specially. If the 
           -- cursor is already on a field on the 
           -- 'Fixed' canvas then we merely show the other
           -- stacked canvas; otherwise, we move the cursor
           -- to the first item on it.
           if target_canvas_name =
          'MY_FIRST_ALT_REG_CANVAS' then
             if curr_canvas_name =
            'TAB_ENTITY_REGIONS_FIXED' then
               show_view(target_canvas_name); 
               go_item(name_in('system.cursor_item'); 
                 -- move focus off the tab itself
             else
               validate(item_scope);
               if not form_success then
                 -- Revert tab to prior value and exit
                 set_canvas_property('TAB_ENTITY_REGIONS',
                       topmost_tab_page, 
                       name_in('system.tab_previous_page'));
                 return;
               end if;
               show_view('MY_FIRST_ALT_REG_CANVAS'); 
                         -- display first stacked canvas
               go_item(
                 'MY_BLOCKNAME.FIRST_ALT_REG_FIRST_FIELD'); 
                  -- go to first item on that stacked canvas
             end if;
           else
             validate(item_scope);
             if not form_success then
               -- Revert tab to prior value and exit
               set_canvas_property('TAB_ENTITY_REGIONS',
                       topmost_tab_page, 
                       name_in('system.tab_previous_page'));
               return;
             end if;
             --
             -- Move to first item on each additional
             -- (non-first) tab
             --
             if target_canvas_name =
               'MY_SECOND_ALT_REG_CANVAS' then
                go_item(
                 'MY_BLOCKNAME.SECOND_ALT_REG_FIRST_FIELD');
             elsif target_canvas_name =
                'MY_THIRD_ALT_REG_CANVAS' then
                 go_item(
                'MY_BLOCKNAME.THIRD_ALT_REG_FIRST_FIELD');
             end if;
           end if;
         else
           show_view(target_canvas_name);
         end if;

Variables for the WHEN-TAB-PAGE-CHANGED Trigger

The following variables are only valid within a WHEN-TAB-PAGE-CHANGED trigger (or code that is called from it):

Validation Checking in WHEN-TAB-PAGE-CHANGED Logic

The validation check is the part of the handler that contains the line:

 validate(item_scope);

followed by code that resets the tab to its original value if the validation fails.

The validate routine is called to force validation of the current field as if the user were tabbing out of the field. That validation includes checking that the field contains a valid value (data type, range of value, and so on) and firing any applicable WHEN-VALIDATE-ITEM logic for the item. The validation check is necessary because the WHEN-TAB-PAGE-CHANGED trigger fires immediately when the user clicks on the tab (any WHEN-VALIDATE-ITEM trigger on the field the user was in before clicking the tab does not get a chance to fire before the WHEN-TAB-PAGE-CHANGED).

If the form is for inquiry only, the validation check is not needed, and you may remove it from the tab handler.

WHEN-TAB-PAGE-CHANGED Variation for Enter-Query Mode

If some fields in your tab region are not queryable, you may need to adjust your logic to allow operation in enter-query mode. All go_item calls must move to Queryable fields, so you would need to test whether the user is in enter-query mode and move to the appropriate field.

Testing for enter-query mode:

 IF :system.mode = 'ENTER-QUERY' THEN ...

Form-level WHEN-TAB-PAGE-CHANGED Trigger

If you only have one set of tabs in your form, call the tab handler from the form-level WHEN-TAB-PAGE-CHANGED trigger and pass the WHEN-TAB-PAGE-CHANGED event:

my_package.tab_my_entity_regions('WHEN-TAB-PAGE-CHANGED');

If you have multiple sets of tabs (multiple tabbed regions), you must have separate tab handlers for each tabbed region. In this case, your form-level WHEN-TAB-PAGE-CHANGED trigger must branch on the current canvas name and call the appropriate tab handler. This branching is only needed if your form has more than one tab canvas. For example:

declare
   the_canvas varchar2(30) := :system.event_canvas;
begin
   if the_canvas = 'FIRST_TAB_REGIONS' then
      control.first_tab_regions('WHEN-TAB-PAGE-CHANGED');
   elsif the_canvas = 'SECOND_TAB_REGIONS' then
      control.second_tab_regions('WHEN-TAB-PAGE-CHANGED');
end if;
end;

Caution About WHEN-TAB-PAGE-CHANGED Event Logic:

Your WHEN-TAB-PAGE-CHANGED code assumes it was called as a result of the user selecting a tab. Tab-related SYSTEM variables are only valid in this mode. If you want to programmatically fire this code, you need to pass a different event and adjust the logic so it does not refer to the tab-related system variables.

WHEN-NEW-ITEM-INSTANCE Logic

The WHEN-NEW-ITEM-INSTANCE branch of the tab handler handles the behavior for a user "tabbing" through all the fields of the block or when Oracle Forms moves the cursor automatically (for example, when a required field is null).

As the cursor moves to a field in a tabbed region with stacked canvases, the stacked canvases raise automatically, but the corresponding tab pages do not. Logic in the WHEN-NEW-ITEM-INSTANCE branch of your tab handler keeps the "topmost" tab page in sync with the current stacked canvas and the current item.

The WHEN-NEW-ITEM-INSTANCE branch is not required for the simple case (items placed directly on the tab pages instead of on stacked canvases). Because the fields are directly on the tab pages, there is no need to programmatically synchronize the tab with the current field. The WHEN-NEW-ITEM-INSTANCE branch is required in all cases where you have stacked canvases (medium and difficult cases). No extra code is required to specifically handle the fixed field canvas.

Text of FNDTABFF.txt WHEN-NEW-ITEM-INSTANCE Branch

Here is the WHEN-NEW-ITEM-INSTANCE branch of the tab handler in the FNDTABFF.txt file:

ELSIF (event = 'WHEN-NEW-ITEM-INSTANCE') THEN
    if ((curr_canvas_name in ('MY_FIRST_ALT_REG_CANVAS',
                             'MY_SECOND_ALT_REG_CANVAS', 
                             'MY_THIRD_ALT_REG_CANVAS')) and
           (curr_canvas_name != current_tab)) then
           set_canvas_property('TAB_ENTITY_REGIONS',
                              topmost_tab_page, 
                              curr_canvas_name);
    end if;

This code relies on the alternative region stacked canvases having exactly the same names as their corresponding tab pages. This code changes the topmost tab using:

set_canvas_property(...TOPMOST_TAB_PAGE...)

The default topmost tab page is the leftmost tab as it appears in the Layout Editor.

Handling Dynamic Tabs

There are two main cases of "dynamic tabs" used in Oracle E-Business Suite:

You can dynamically hide tabs at form startup using Set_Tab_Page_Property(...VISIBLE...).

You can dynamically enable or disable tabs during a form session using Set_Tab_Page_Property(...ENABLED...). You typically add code elsewhere in your form that enables or disables tabs based on some condition.

Use Get_Tab_Page_Property for testing whether a tab is enabled or disabled:

 DECLARE
    my_tab_page_id   TAB_PAGE;
    my_tab_enabled   VARCHAR2(32);
  BEGIN
    my_tab_page_id := FIND_TAB_PAGE('my_tab_page_1');
    my_tab_enabled := GET_TAB_PAGE_PROPERTY (my_tab_page_id,
                      ENABLED) 
    IF my_tab_enabled= 'TRUE' THEN ...

Note that you cannot hide or disable a tab page if it is currently the topmost page.

Dynamic Tabs with a "Master" Field

The case of a "master" field, whose value controls enabling and disabling of tabs, requires special logic. The logic must account for user clicking onto the tab that should now be disabled. In this case, the UI should act as if tab really was disabled.

How the situation occurs: suppose you have a field (either on a tab page or not) where, based on the value of the field, a tab is enabled or disabled. If the master field is a poplist, check box, or option group, the enable/disable logic should be in the WHEN-LIST-CHANGED or equivalent trigger.

There is a corner case that must be addressed differently: when your master field is a text item. In this situation the user changes the value of the master field such that the tab would be disabled, but then clicks on that (still-enabled) tab before the field's WHEN-VALIDATE-ITEM logic would fire (that is, the user clicks on the tab instead of tabbing out of the field, which would ordinarily fire the WHEN-VALIDATE-ITEM logic).

Because the WHEN-VALIDATE-ITEM logic has not yet fired when the user clicks on the tab, the tab is still enabled. However, the behavior for the end user should be as if the tab was disabled and as if the user never clicked on the disabled tab (the tab should not become the topmost tab). Because tabs get focus immediately upon clicking, the should-be-disabled tab immediately becomes the topmost tab, at which point it must be programmatically disabled and the tabbed region returned to its previous state upon validation of the master field. However, the tab cannot be disabled while it is the topmost tab and has focus.

The validate(item_scope) logic in the WHEN-TAB-PAGE-CHANGED part of the tab handler fires the WHEN-VALIDATE-ITEM logic of the field. The WHEN-VALIDATE-ITEM logic cannot access the :system.tab_previous_page variable needed to revert the tab page to the previous page (before the user clicked). The tab handler must therefore contain code to store the topmost tab page information in a package variable or form parameter at the end of each successful tab page change. This stored value can then be used to revert the topmost tab in case of a failed tab page change (where the failure is the result of the WHEN-VALIDATE-ITEM logic). The tab handler must be modified to do nothing (after the validate call) if the clicked-on tab is disabled.

Other Code You May Need

You may need to add tab-related code for the following triggers:

KEY-CLRFRM

Depending on the desired form behavior, you may want to reset the tab pages to their initial state after a KEY-CLRFRM. You would add a branch for KEY-CLRFRM to your handler and include something like the following code:

set_canvas_property('TAB_ENTITY_REGIONS', topmost_tab_page,
                     'MY_FIRST_ALT_REG_CANVAS');  
                     -- reset the tabs after KEY-CLRFRM
show_view('MY_FIRST_ALT_REG_CANVAS');  
                     -- display the first stacked canvas

WHEN-NEW-FORM-INSTANCE or PRE-FORM

You may also have branches for WHEN-NEW-FORM-INSTANCE or PRE-FORM that initialize the tabbed region such as by doing a show_view.

Oracle Forms does not guarantee canvas sequencing. You may need to include extra show_view() commands at form startup or elsewhere in your form to get proper canvas sequencing.

Testing Tab Page Properties

The Oracle Forms Set/Get_tab_page_property (canvas.tabpage...) built-in routines use these properties:

Use the Get_Tab_Page_Property routine for testing whether a tab is enabled or disabled:

DECLARE
  my_tab_page_id   TAB_PAGE;
  my_tab_enabled        VARCHAR2(32);
BEGIN
  my_tab_page_id := FIND_TAB_PAGE('my_tab_page_1');
  my_tab_enabled := GET_TAB_PAGE_PROPERTY (my_tab_page_id, ENABLED) 
  IF my_tab_enabled= 'TRUE' THEN ...

Setting and Getting Topmost Tab Pages

This example sets the topmost tab page (that is, the displayed tab page) of the TAB_ENTITY_REGIONS tab canvas to be the MY_SECOND_TAB_PAGE tab page:

set_canvas_property('TAB_ENTITY_REGIONS', topmost_tab_page,
                     'MY_SECOND_TAB_PAGE'); 

You can also retrieve the name of the current tab page:

current_tab        VARCHAR2(30) := get_canvas_property('TAB_ENTITY_REGIONS',
                   topmost_tab_page);

Coding Alternative Region Behavior

In Oracle E-Business Suite Release 12, alternative regions are replaced by tabbed regions. You should implement tabbed regions for all new code.

Alternative Regions

A block with multiple regions that cannot be rendered simultaneously uses a series of stacked canvases to display each region, one at a time, within a single region boundary. These stacked regions are called "Alternative Regions".

For more information, see the Oracle E-Business Suite User Interface Standards for Forms-Based Products.

Each alternative region has a poplist control element containing all possible regions for that block.

Behavior of the Alternative Region Poplist

Alternative region poplists should behave according to the following standards:

Example: Coding an Alternative Region

Block LINES has some fields on a content canvas ORDER. The last of these fields is ITEM.

LINES has alternative regions on canvases LINES_PRICE and LINES_ITEM. The regions are accessible only if LINES.ITEM is not null. The first item of LINES_PRICE is LIST_PRICE. The first item of LINES_ITEM is DESCRIPTION.

  1. Create a poplist in a control block to select the current region. The poplist should be queryable and non-navigable. The poplist should display a friendly name for each region with a corresponding value equal to the region's canvas name.

    The block CONTROL has a queryable, non-navigable poplist named LINES_REGIONS (block name plus _REGIONS) that contains the following values, with the internal value following the displayed value: Price Information (LINES_PRICE), Item Information (LINES_ITEM).

  2. Visit the CONTROL block:

    At form startup, you must visit the block containing the control poplist to instantiate it:

    • Create a text item called DUMMY as the first item in the CONTROL block. Make the text item Visible, Enabled and Keyboard Navigable, Position 0,0, WD=0, HT=0, and +-Length=1. Place it on the first canvas to be displayed.

    • In WHEN-NEW-FORM-INSTANCE, make two GO_BLOCK() calls, one to the CONTROL block and another to the First Navigation Block.

    • Make sure you do similar GO_BLOCK calls in the code where you handle KEY-CLRFRM.

  3. Setting the First Displayed Region:

    Within Oracle Forms Designer, designate the first stacked canvas of the set of alternative regions to show as displayed; make all other canvases in the set not displayed to improve startup performance.

    You must sequence the stacked canvases carefully by ordering the canvases within the list in the Oracle Forms Object Navigator (the first stacked canvas in the list is the first stacked canvas displayed). In addition, you must sequence your items to have the correct order when a user tabs through the fields on the alternative regions.

    Tip: When stacked canvases are referenced, their sequence may be unpredictable. In this case, issue a SHOW_VIEW at form startup, or whenever the window is first displayed, to force the proper canvas to render.

    Make sure your stacked canvas views are all exactly the same size and occupy exactly the same space on the content canvas.

  4. Create your item handler procedures to control which region displays as in the following example. Remember, in our example form, we want to disallow access to the regions unless the field LINES.ITEM is not null:

    PACKAGE BODY control IS 
       
      g_canvas_name  VARCHAR2(30) := null; 
      PROCEDURE lines_regions(event varchar2) IS 
        target_canvas_name VARCHAR2(30); 
        curr_canvas_name VARCHAR2(30) :=         
                        get_item_property(:system.cursor_item,
                                          ITEM_CANVAS); 
      BEGIN 
        IF (event = 'WHEN-NEW-ITEM-INSTANCE') THEN  
          -- Check if the poplist and the canvas are out of synch  
          -- to prevent flashing if they are not.   
          IF  ((curr_canvas_name in ('LINES_PRICE', 'LINES_ITEM')) AND
               (curr_canvas_name != :control.lines_regions)) THEN 
            :control.lines_regions := curr_canvas_name; 
            g_canvas_name := curr_canvas_name; 
          END IF; 
        ELSIF (event = 'WHEN-LIST-CHANGED') THEN 
          target_canvas_name := :control.lines_regions; 
          -- The following is optional code to disallow access  
          -- to certain regions under certain conditions
          -- Check that the region is accessible.  Always allow access
          -- during queries. 
          IF (:SYSTEM.MODE = 'ENTER-QUERY') THEN 
            null; 
          ELSE 
            IF (:lines.item is null) THEN 
              FND_MESSAGE.SET_NAME('OE', 'OE_ENTER_ITEM_FIRST'); 
              FND_MESSAGE.ERROR; 
              :control.lines_regions := g_canvas_name; 
              RAISE FORM_TRIGGER_FAILURE; 
            END IF; 
       -- End of optional code
          END IF; 
          -- Display the region.  If in the same block, go to the
          -- first item in the region.  
            IF curr_canvas_name in ('LINES_PRICE', 'LINES_ITEM') THEN 
              hide_view(curr_canvas_name); 
            END IF; 
            show_view(target_canvas_name); 
            IF (:system.cursor_block = 'LINES') THEN  
              IF (target_canvas_name = 'LINES_PRICE') THEN 
                -- Go to the first item in the canvas LINES_PRICE
                go_item('lines.list_price'); 
              ELSIF (target_canvas_name = 'LINES_ITEM') THEN 
                -- Go to the first item in the canvas LINES_ITEM
                go_item('lines.description'); 
              END IF; 
            END IF; 
            g_canvas_name := target_canvas_name; 
        ELSE 
            fnd_message.debug('Invalid event passed to
                  control.lines_regions'); 
        END IF; 
      END lines_regions; 
    END control; 
    

    After the user displays the LOV via KEY-MENU and chooses a value from the list, the WHEN-LIST-CHANGED handler switches the regions.

  5. Call the following triggers:

    Trigger: Block-level WHEN-NEW-ITEM-INSTANCE on the LINES block:

    CONTROL.LINES_REGIONS('WHEN-NEW-ITEM-INSTANCE');
    

    Trigger: Block-level KEY-MENU on the LINES block (Execution Hierarchy: Override):

    IF APP_REGION.ALT_REGIONS('CONTROL.LINES_REGIONS') THEN
      CONTROL.LINES_REGIONS('WHEN-LIST-CHANGED');
    END IF;
    

    Trigger: Item-level WHEN-LIST-CHANGED on CONTROL.LINES_REGIONS.

    CONTROL.LINES_REGIONS('WHEN-LIST-CHANGED');
    

    These triggers should fire in ENTER-QUERY mode.

Controlling Records in a Window

This section discusses

Duplicating Records

Why Duplicate Record is Disabled by Default

By default, duplicate record is disabled at the form level. There are several reasons for this:

For any block where you want to enable Duplicate Record, you must write code. You must process unique keys, possibly reapply defaults, and confirm that copied data is still valid. None of this is done by default, and this can lead to errors or data corruption.

In general, duplicate all item values, even if the item value must be unique. The user may wish to create a unique value very similar to the previous value.

Do not override a default if

Example

A block order has items order_number and order_date which are defaulted from the sequence order_S and from SYSDATE respectively, and which cannot be modified by the user. The item status should contain "Open" for a new order, but the user can change the Status to "Book" at any time to book the order.

  1. Create your event handler procedures as follows:

    PACKAGE BODY order IS
       PROCEDURE KEY_DUPREC IS
       CURSOR new_order_number IS SELECT order_S.nextval
                                   FROM sys.dual;
      BEGIN
        DUPLICATE_RECORD;
        open new_order_number;
        fetch new_order_number into :order.order_number;
        close new_order_number;
        :order.status : = 'Open';
        :order.order_date := FND_STANDARD.SYSTEM_DATE;
        :order.row_id := null;
      END KEY_DUPREC;
    END order;
    
  2. Call your event handler procedures in:

    Trigger: KEY-DUPREC on order:

    order.KEY_DUPREC;
    

Renumbering All Records in a Window

To renumber an item sequentially for all records on the block, create a user-named trigger to increment the sequence variable and set the sequence item. Use the procedure APP_RECORD.FOR_ALL_ RECORDS to fire the trigger once for each record.

To number an item sequentially as records are created, create a variable or item to contain the sequence number. Create a WHEN-CREATE- RECORD trigger to increment the sequence variable and default the sequence item. However, if you want to renumber all the records in a window, use the procedure APP_RECORD.FOR_ALL_RECORDS.

If you are renumbering your items after a query or commit, you may wish to reset the record status so that the record is not marked as changed.

Example

A block lines has item line_number. When a record is deleted, line_number must be renumbered.

  1. Create your item handler procedures as follows:

     PACKAGE BODY lines IS
         line_number_seq number := 0;
         PROCEDURE delete_row IS
         BEGIN
           line_number_seq := 0;
           APP_RECORD.FOR_ALL_RECORDS('reseq_line_number');
         END delete_row;
       END lines;
    
  2. Create a user-defined trigger RESEQ_LINE_NUMBER as follows:

    lines.line_number_seq := lines.line_number_seq + 1;
    :lines.line_number := lines.line_number_seq;
    
  3. Call your item handler procedures in:

    Trigger: KEY-DELETE:

    lines.line_number('KEY-DELETE');

    Warning: Be aware of the consequences of this type of processing. Specifically, consider the following points:

    If a very large number of records are queried in a block, looping through them one at a time can be very slow.

    Not all the records that could be in the block may be in the current query set if you allow the user to enter the query.

    If you are changing a value that is part of a unique key, you may get errors at commit time because the record currently being committed may conflict with another already in the database, even though that record has also been changed in the block.

Passing Instructions to a Form

To pass information when navigating from one form to another when both forms are already open, use the WHEN-FORM-NAVIGATE trigger. You do not code this trigger directly; instead pass the information through global variables.

To use this trigger, populate a global variable called GLOBAL.WHEN_FORM_NAVIGATE with the name of a user-named trigger. When a form is navigated to, this trigger fires.

The WHEN-FORM-NAVIGATE trigger fires upon programmatically navigating to a form using the GO_FORM built-in. Accordingly, this trigger is referenced into all forms.

Querying an Item

It often makes sense to navigate to a form and query on a specific item. For example, suppose you have an Order Entry form ORDERS and a Catalog form CATALOGS. You want to navigate from the ORDERS form to CATALOGS and query up a specific part number.