Tutorial 44: How to build a Tree driven Tabbed Application

Note: Formspider v1.1 or higher is required to practice this tutorial.

This tutorial describes how to build a simple reporting application with a static header section at the top, a navigation tree displaying various reports and report folders at the left and a dynamic content section that changes depending on the selected report node from the tree at the right side of the screen. This classical and frequently needed design may also serve you as a template that you can use as a starting point to build your business application.

When completed, the application looks like the following:

Simple Reporting Application

You may click here to run the application.

To import this application directly into your local installation:

  1. Download the reportAppDemo.zip file.
  2. Follow the steps described in the installation guide which provides instructions on how to install this application.

Open Formspider IDE and click “New Application” under the “File” menu, the “New Application” dialog shows up. Enter “reportAppDemo” as the name of the application and “HR” as datasource schema name. Leave other fields empty and click “OK”. This creates the “reportAppDemo” application with a default mainframe (called “mainFrame”) and a default panel (called “mainPanel”).

Implementing the Header

First, implement the header section of the application. This section contains an image displaying the company logo, a welcome textLabel displaying the currently logged in user information, a log out hyperlink/button pair that logs out the user from the application and a horizontal line that separates the header section from the navigation tree and content section.

Expand the “Containers” accordion, select the “Panels” node in the navigation tree and click the “+” button to create a new panel. Alternatively, you may right click the “Panels” node and select the “New” menu item from the pop-up menu. The “New Panel” dialog shows up. Enter “headerPanel” as the name of the panel. Click “OK” to create and open the panel in the editor.

Creating “headerPanel”

Add an image component displaying the Formspider logo (fs_logo_646_220_transparent.png). The image component can display an image with the given URL or an image file that is locally stored in the middle-tier by using the relative path of the image file as the URL value. To display an image that is locally stored in the middle-tier:

  • Create a folder named “reportAppDemo” under the “apps” folder existing in the middle-tier installation.
  • Under the “apps\reportAppDemo” folder, create a new folder with a name of your choice (“images” for example).
  • Download and copy the (fs_logo_646_220_transparent.png file under the “apps\reportAppDemo\images” folder.

Add a textLabel displaying a welcome message to the logged in user, a hyperlink labeled “Log out” and a button labeled “X” logging out the current user from the application and a horizontal line. The “headerPanel” XML should like the following:

<panel>
  <tableLayout>
    <row height="55">
      <cell hAlign="Left" vAlign="Center" childWidth="162" childHeight="55">
        <image url="apps/reportAppDemo/images/fs_logo_646_220_transparent.png"/>
      </cell>
      <cell hAlign="Full" vAlign="Center">
        <textLabel font-style="Bold" name="welcomeTextLabel" label="Welcome DemoUser" text-align="Right"/>
      </cell>
      <cell width="5"/>
      <cell width="50" hAlign="Full" vAlign="Center">
        <hyperLink font-style="Bold" text-align="Right" label="Log out"/>
      </cell>
      <cell width="18" hAlign="Right" vAlign="Center" childWidth="16" childHeight="16">
        <button font-color="white" font-style="Bold" label="X" backgroundColor="Black"/>
      </cell>
    </row>
    <row height="4">
      <cell columnSpan="5" hAlign="Full" vAlign="Full">
        <line color="#e16801"/>
      </cell>
    </row>
  </tableLayout>
</panel>

Implementing the Report Tree

The report tree contains child report nodes and their parent report folders nodes named “Management Reports”, “Business Reports” and “HR Reports”. Before creating the necessary datasource definition and datasource for this tree, you will implement two functions that will be used in the query of the datasource definition.

In your datasource schema, create a package called “reportAppDemo_pkg” and open your newly created “reportAppDemo_pkg” package in your favorite PL/SQL editor. Add a function named “leaf” and ensure that the procedure is exposed in the package specification. This function simply returns the node state constant named “leaf” that is defined in the api_treeNode package specification to make you able to use this PL/SQL constant in the SQL query.

function leaf return varchar2 is
begin
  return api_treeNode.leaf;
end;

Add a function named “collapsed” and ensure that the procedure is exposed in the package specification. This function returns the node state constant named “collapsed” that is defined in the api_treeNode package specification.

function collapsed return varchar2 is
begin
  return api_treeNode.collapsed;
end;

Expand the “Datasource Definitions” accordion, select the “Datasource Definitions” node and click the “+” button. Alternatively, you may right click the “Datasource Definitions” node and select “New” from the pop up menu. The “New Datasource Definition” dialog shows up.

Enter “reportsTree” as “Name” and select “Query” radio button from “Based On” section, this selection means that you will enter a SQL query to retrieve data.

Creating “reportsTree” datasource definition

To enter your SQL query click the “Query” node in the navigation tree on the left. Enter the following SQL Statement to the datasource definition:

select name_tx,
       report_id,
       reportFolder_yn,
       parent_id,
       decode(reportFolder_yn,
              'N',
              'apps/reportAppDemo/images/report.png',
              'apps/reportAppDemo/images/reportfolder.png'
              ) iconPath_tx,
       decode(reportFolder_yn,
              'N',
              'Plain',
              'Bold'
              ) fontStyle_cd,
       decode(reportFolder_yn,
              'N',
              reportappdemo_pkg.leaf,
              reportappdemo_pkg.collapsed
              ) nodestate_cd
from(
      -- Report Folders --
      select 'Management Reports' as name_tx, 1 as report_id, 'Y' as reportFolder_yn, null as parent_id from dual
      union all
      select 'Business Reports'  , 2, 'Y', null from dual
      union all
      select 'HR Reports'        , 3, 'Y', null from dual
      union all
      -- Management Reports --
      select 'Management Report1', 4, 'N', 1 from dual
      union all
      select 'Management Report2', 5, 'N', 1 from dual
      union all
      select 'Management Report3', 6, 'N', 1 from dual
      union all
      -- Business Reports --
      select 'Business Report1'  , 7, 'N', 2 from dual
      union all
      -- HR Reports --
      select 'HR Report1'        , 8, 'N', 3 from dual
      union all
      select 'HR Report2'        , 9, 'N', 3 from dual
    )
where (:parentReport_id is not null and parent_id = :parentReport_id)
       or
      (:parentReport_id is null and parent_id is null)

Entering Query for “reportsTree” datasource definition

Following this query, the initial node state of a report folder node (parent node) is collapsed, it uses an image file named “reportfolder.png” as its node icon and “Bold” as its font-style. On the other hand, the initial node state of a report node (child node) is leaf, using an image file named “report.png” as its node icon and “Plain” as its font-style.

Download the reportfolder.png and report.png files and copy them under the “apps\reportAppDemo\images” folder that you have created earlier in your middle-tier installation.

Note that this query contains a bind variable named “parentReport_id” that is used to populate child nodes belonging to a single parent report folder. Click the “Bind Variables” node to register “parentReport_id” bind variable. Press “New BindVar” button, a new row appears in the grid. Enter “parentReport_id” as the “Name” and select “DefaultNumber” as the “Domain” value.

Creating “parentReport_id” bind variable

Click “OK” to save and close the “New Datasource Definition” dialog. This creates the “reportsTree” datasource definition and the “reportsTree1 datasource. You will use the “reportsTree1” datasource to populate child report nodes belonging to a parent report folder.

Create a new panel, enter “reportsTreePanel” as the name and “White” as backgroundColor. Add a textLabel with “Reports” as value and a tree named “tree_reports”. This tree uses the “reportsTree1” datasource:

<panel backgroundColor="White">
  <tableLayout cellSpacing="4">
    <row height="20"/>
    <row height="20">
      <cell hAlign="Full" vAlign="Center">
        <textLabel font-style="Bold" font-size="13" label="Reports" text-align="Center"/>
      </cell>
    </row>
    <row height="3">
      <cell hAlign="Full" vAlign="Center">
        <line/>
      </cell>
    </row>
    <row height="20"/>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <tree font-size="12" name="tree_reports" displayColumn="NAME_TX" nodeKeyColumn="REPORT_ID" iconColumn="ICONPATH_TX" fontStyleColumn="FONTSTYLE_CD" dataSource="reportsTree1" nodeStateColumn="NODESTATE_CD"/>
      </cell>
    </row>
  </tableLayout>
</panel>

The next step is populating the appropriate child report nodes when a parent report folder node is expanded. To achieve this, you will create a Formspider action which fires when a tree node is expanded.

Open the “reportAppDemo_pkg” package and add a procedure named “expandReportsTree”. This procedure first checks whether the expanded tree node is previously expanded or not using the api_treeNode.isPrevExpanded API. If it’s not expanded before, it sets the “parentReport_id” bindvariable and populates the child nodes of the expanded parent node using api_treeNode.populateChildren API.

procedure expandReportsTree is
  v_nodeKey_tx varchar2(255);
begin
  v_nodeKey_tx := api_tree.getexpandednodekey('reportsTreePanel.tree_reports');
  -- if this node is not expanded before, populate its child nodes
  if api_treenode.isprevexpanded('reportsTreePanel.tree_reports', v_nodeKey_tx) = 'N' then
    api_datasource.setbindvar('reportsTree1.parentReport_id', to_number(v_nodeKey_tx));
    api_treenode.populatechildren('reportsTreePanel.tree_reports', v_nodeKey_tx);
  end if;
end;

In the Formspider IDE, expand the “Actions” accordion, select the “Actions” node, click the “+” button to create a new action. Alternatively you may right click the “Actions” node and select “New” from the pop up menu. The “New Action” dialog shows up. Enter “expandReportsTree” as the action name and “reportAppDemo_pkg.expandReportsTree” as the procedure. Click “OK” to save your action.

Creating “expandReportsTree” action

Open “reportsTreePanel” XML and add an expanded event to the “tree_reports” tree, triggering the “expandReportsTree” action, the panel XML should look like;

<panel backgroundColor="White">
  <tableLayout cellSpacing="4">
    <row height="20"/>
    <row height="20">
      <cell hAlign="Full" vAlign="Center">
        <textLabel font-style="Bold" font-size="13" label="Reports" text-align="Center"/>
      </cell>
    </row>
    <row height="3">
      <cell hAlign="Full" vAlign="Center">
        <line/>
      </cell>
    </row>
    <row height="20"/>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <tree font-size="12" name="tree_reports" displayColumn="NAME_TX" nodeKeyColumn="REPORT_ID" iconColumn="ICONPATH_TX" fontStyleColumn="FONTSTYLE_CD" dataSource="reportsTree1" nodeStateColumn="NODESTATE_CD">
          <events>
            <expanded action="expandReportsTree"/>
          </events>
        </tree>
      </cell>
    </row>
  </tableLayout>
</panel>

Implementing the Dynamic Content Section

As the application content, each report will be displayed in a separate tab, using a tabbedPanel. A report tab will be dynamically added to this tabbedPanel when the related report tree node is clicked.

Create a tabbedPanel where the report tabs will be added. Create a new panel, enter “reportsTabsPanel” as the name and select “TabbedPanel” from the “Type” combobox.

Creating “reportsTabsPanel”

Next, create a panel for each report existing on the report tree. Create a new panel and enter “managementReport1Panel” as the name. Add a textLabel with “Management Report1” as value:

<panel>
  <tableLayout>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <textLabel font-style="Bold" font-size="20" label="Management Report1" text-align="Center"/>
      </cell>
    </row>
  </tableLayout>
</panel>

Repeat the same process for each of the remaining reports named “Management Report2”, “Management Report3”, “Business Report1”, “HR Report1” and “HR Report2”. Create the panels “managementReport2Panel”, “managementReport3Panel”, “businessReport1Panel”, “hrReport1Panel” and “hrReport2Panel”, with the following XMLs:

managementReport2Panel

<panel>
  <tableLayout>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <textLabel font-style="Bold" font-size="20" label="Management Report2" text-align="Center"/>
      </cell>
    </row>
  </tableLayout>
</panel>

managementReport3Panel

<panel>
  <tableLayout>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <textLabel font-style="Bold" font-size="20" label="Management Report3" text-align="Center"/>
      </cell>
    </row>
  </tableLayout>
</panel>

businessReport1Panel

<panel>
  <tableLayout>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <textLabel font-style="Bold" font-size="20" label="Business Report1" text-align="Center"/>
      </cell>
    </row>
  </tableLayout>
</panel>

hrReport1Panel

<panel>
  <tableLayout>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <textLabel font-style="Bold" font-size="20" label="HR Report1" text-align="Center"/>
      </cell>
    </row>
  </tableLayout>
</panel>

hrReport2Panel

<panel>
  <tableLayout>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <textLabel font-style="Bold" font-size="20" label="HR Report2" text-align="Center"/>
      </cell>
    </row>
  </tableLayout>
</panel>

To add a report panel as a new tab to the “reportsTabsPanel” you will create a Formspider action which fires when a tree node is left clicked.

Open the “reportAppDemo_pkg” package and add a procedure named “leftClickReportsTree”. This procedure checks if the clicked tree node is a report node or a report folder node and executes the following:

  • If it’s a report node, it adds the clicked report as a new tab to the tabbedPanel if it’s not already added. If the report is already added, it sets the related report tab as the current tab.
  • If the clicked node is a report folder node, it expands the folder node and populate the child nodes if necessary.
procedure leftClickReportsTree is
  v_nodeKey_tx          varchar2(255);
  v_newTabOrder_nr      number;
  v_existingTabOrder_nr number;
  v_nodeState_cd        varchar2(255);

  function getPanelNameByReportId(in_report_id number) return varchar2 is
  begin
    return  case in_report_id
              when 4 then
                'managementReport1Panel'
              when 5 then
                'managementReport2Panel'
              when 6 then
                'managementReport3Panel'
              when 7 then
                'businessReport1Panel'
              when 8 then
                'hrReport1Panel'
              when 9 then
                'hrReport2Panel'
              end;

  end;

begin
  v_nodeKey_tx   := api_tree.getselectednodekey('reportsTreePanel.tree_reports');
  v_nodeState_cd := api_treenode.getstate('reportsTreePanel.tree_reports', v_nodeKey_tx);
  if v_nodeState_cd = leaf then -- report node
    begin
      -- if report tab is already added, set it as current tab
      v_existingTabOrder_nr := api_tabbedpanel.gettaborder('reportsTabsPanel', 'tab_' || v_nodeKey_tx);
      api_tabbedpanel.setcurrenttab('reportsTabsPanel', v_existingTabOrder_nr);
    exception
      when api_exception.e_invalidTabName then -- report tab does not exist, add it
        -- get order of the tab to be added
        v_newTabOrder_nr := nvl(api_session.getvaluenr('tabOrder_nr'), 0) + 1;
        api_session.add('tabOrder_nr', v_newTabOrder_nr);
        -- add tab
        api_tabbedpanel.addtab(in_tabbedpanelname_tx => 'reportsTabsPanel'
                              ,in_order_nr           => v_newTabOrder_nr
                              ,in_tabname_tx         => 'tab_'||v_nodeKey_tx
                              ,in_title_tx           => api_treenode.getdisplay('reportsTreePanel.tree_reports', v_nodeKey_tx)
                              ,in_panelname_tx       => getPanelNameByReportId(v_nodeKey_tx)
                              ,in_closable_yn        => 'Y'
                              );
        -- set the added tab as the current tab
        api_tabbedpanel.setcurrenttab('reportsTabsPanel', v_newTabOrder_nr);
    end;
  -- collapsed report folder node
  elsif v_nodeState_cd = collapsed then
    -- if this node is not expanded before, populate its child nodes
    if api_treenode.isprevexpanded('reportsTreePanel.tree_reports', v_nodeKey_tx) = 'N' then
      api_datasource.setbindvar('reportsTree1.parentReport_id', to_number(v_nodeKey_tx));
      api_treenode.populatechildren('reportsTreePanel.tree_reports', v_nodeKey_tx);
    end if;
    -- expand the left clicked node
    api_treenode.expand('reportsTreePanel.tree_reports', v_nodeKey_tx);
  end if;
end;

Create a new action, enter “leftClickReportsTree” as the action name and “reportAppDemo_pkg.leftClickReportsTree” as the procedure. Click “OK” to save your action. Open “reportsTreePanel” XML and add an leftClick event to the “tree_reports” tree, triggering the “leftClickReportsTree” action:

<panel backgroundColor="White">
  <tableLayout cellSpacing="4">
    <row height="20"/>
    <row height="20">
      <cell hAlign="Full" vAlign="Center">
        <textLabel font-style="Bold" font-size="13" label="Reports" text-align="Center"/>
      </cell>
    </row>
    <row height="3">
      <cell hAlign="Full" vAlign="Center">
        <line/>
      </cell>
    </row>
    <row height="20"/>
    <row>
      <cell hAlign="Full" vAlign="Full">
        <tree font-size="12" name="tree_reports" displayColumn="NAME_TX" nodeKeyColumn="REPORT_ID" iconColumn="ICONPATH_TX" fontStyleColumn="FONTSTYLE_CD" dataSource="reportsTree1" nodeStateColumn="NODESTATE_CD">
          <events>
            <expanded action="expandReportsTree"/>
            <leftClick action="leftClickReportsTree"/>
          </events>
        </tree>
      </cell>
    </row>
  </tableLayout>
</panel>

For usability, display the navigation tree section and the content section in a splitPanel, with the tree section at the left side and the content section at the right side of the screen.

Create a new panel, enter “mainSplitPanel” as the name, select “SplitPanel” from the “Type” combobox and enter “320” as “Divider Location” value. Click “OK” to save your panel.

Creating “mainSplitPanel”

Include the “reportsTreePanel” to the left and “reportsTabsPanel” to the right:

<splitPanel dividerLocation="320">
  <left panelName="reportsTreePanel"/>
  <right panelName="reportsTabsPanel"/>
</splitPanel>

Expand the “Panels” node from the “Containers” tree and double click the “mainPanel” to open it in the editor. Include the “headerPanel” and the newly created “mainSplitPanel” to the “mainPanel”:

<panel>
  <tableLayout>
    <row height="59">
      <cell width="10"/>
      <cell hAlign="Full" vAlign="Full">
        <include panelName="headerPanel"/>
      </cell>
      <cell width="10"/>
    </row>
    <row>
      <cell/>
      <cell hAlign="Full" vAlign="Full">
        <include panelName="mainSplitPanel"/>
      </cell>
      <cell/>
    </row>
  </tableLayout>
</panel>

Finally, expand the “Windows” node and then the “Mainframe” node from the “Containers” tree. Double click to the “mainFrame” node to open it on the editor, set the value of the “maximizeOnStartUpattribute” to “Y” and “titleBarVisible” to “N”:

 <mainframe title="Reports Application" width="500" height="500" maximizeOnStartUp="Y" titleBarVisible="N">
  <borderLayout>
    <cell docking="Center">
      <include panelName="mainPanel"/>
    </cell>
  </borderLayout>
</mainframe>

Press “Run on Web” button to run the application. Expand the report folder node named “Management Report”, its child report nodes are displayed. Click on the report nodes named “Management Report1” and “Management Report2”, note that these two reports are added as new tabs to the the content section. Click on the report node “Management Report1” again, note that this time, the report tab is set as the current tab of the tabbedPanel.

You may click here to run the application.

Created Application is running