Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
siarhei_pisarenka3
Active Contributor
0 Kudos

Preface

I'll start with the fact that the whole last month I've been heavily participating in some strange and interesting Web Dynpro application development. What was the interesting in that development? The first thing is the Java application was completely based on ABAP backend. The application loaded all the necessary data from the backend, user worked on the data, modified it and sent it back to R3. Nothing unusual till the moment. Right?... But, there was the serious restriction that I was not allowed to change anything in the backend logic, I could not request change in the BAPI interface or implementation. It was just a black box for me.

Horizontal Table, but Vertical Data 

One of the requirements was to design a horizontal table. I mean the table which displays a data in row manner. The peculiar feature of such table is a horizontal column expansion with the column scroll bar:

Horizontal table with scrollable columns

I began working on the table with enthusiasm, but almost immediatelly realized that I did not have such BAPI which would return me the data exactly as I'd like to display... No, Wrong... I had a BAPI, but its output format was in usual table form, not as I required. So, since I could not obtain another BAPI I had to convert the BAPI output (see below) into a horizontal representation and display it on UI:

YearSalaryProfit
1/30/20041,21093,243
1/30/20051,78968,041
1/30/20061,43718,815
1/30/20071,86148,302
1/30/20081,12328,766

Existing Solutions 

After a while searching throught SDN forums in forum Display table in row wise it's mentioned two solutions:

  • Purely read-only table can be achieved by programatical creating a table layout. Actually you can create not exactly Web Dynpro table, but just a set of cells (TextView controls) placed inside Grid or Matrix container. Umm... Not my case: I needed editable table.
  • Rearranging the vertical context node into a horizontal one and binding it to the table. Good idea, but here we have to solve additionally two main problems: Data synchronization between the Vertical and Horizontal nodes and support for dynamic number of columns, because the number is not known on design time.

So I decided to enhance the second solution and overcome the mentioned issues.

Generic Idea

I used standard Table control partly designed in studio and partly generated at runtime. The main idea is to bind the table columns to set of calculated attributes in order to represent the table data in 'horizontal' form. The calculated attributes are also generated at runtime. Getter/setters for the attributes are impemented as calculated attribute accessors. For the purpose WebDynpro provides special interface IWDCalculatedAttributeAccessor

Two context nodes: Original and Inverted

In the context below AnnualIndicators(0..N) contains the original data in 'vertical' form. This node could be in any form (value or model) and is completely determined by the data source (BAPI output, like in my case, or Web Service output, etc.). Node HorizontalTableData(0..N) is designed specially for the table representation and bound to the table control. The node contains 'the same data' as in AnnualIndicators, but inverted in the way where columns of AnnualIndicators becoming rows in HorizontalTableData:

Context structure for table data source

Supply function for node HorizontalTableData is called just one time independently from AnnualIndicators. N of elements is less or equals to N of columns in AnnualIndicators

public void supplyTableData(IPrivateHorizontalTableView.IHorizontalTableDataNode node, IPrivateHorizontalTableView.ITableDataElement parentElement)  {
//@@begin supplyTableData(IWDNode,IWDNodeElement)  
     final String[] VERTICAL_ATTRS = { "salary", "profit" };  
     final String[] VERTICAL_ATTR_LABELS = { "Salary", "Profit" };    
     for (int i = 0; i<VERTICAL_ATTRS.length; i++) {       
          IHorizontalTableDataElement indicatorEl = node.createHorizontalTableDataElement();       
          indicatorEl.setIndicator(VERTICAL_ATTR_LABELS[i]);       
          node.addElement(indicatorEl);   
     }   
//@@end 

Calculated Attribute Accessors

Calculated attribute accessors allows node HorizontalTableData to have an access to the data stored physically in node AnnualIndicators. In other words, elements of HorizontalTableData do not keep any data in attributes, almost all the attributes are calculated and read/write data from/to AnnualIndicators. In fact the accessors implement absolutely transparent data mapping between AnnualIndicators and HorizontalTableData. Please, see the source code of the acccessors at the end of the blog. The Java class is universal, not applied to any concrete tables or context nodes. It can be completely reused in other UI components.

Table Design

The table on design time contains just one read-only column Indicators. The first column has fixed position on the left and plays role of a header like in a standard table:

Initial table design

  • Table property 'dataSource' is bound to TableData.HorizontalTableData.
  • Table property 'scrollableColCount' is 7 in my example. It activates table Column Scroll Bar. If the number of visible scrollable columns is more then 7 user can scroll right.
  • Property 'fixedPosition' of the first column is set to 'left'.
  • Property 'text' of the TextView is bound to TableData.HorizontalTableData.indicator.

Generation of Table rest

All other table columns are generated dynamically at runtime, but the generation is performed just one time during the view construction. Each generated column displays data extracted from the corresponding element of node AnnualIndicators. However, the number of dynamical columns is pure statical constant and does not depend actually on the number of 'vertical' rows. I mean that we generate some hardcoded maximal static number of columns the table is ever able to display. At runtime we will just hide unnecessary columns by manipulating Visibility property. If there is no such 'vertical' row in AnnualIndicators the corresponding column will be invisible:

HorizontalCalcAttrAccessors.java:

/*
* Created on 02.11.2009
*/
package com.epam.test.ui.table.row;
import com.sap.tc.webdynpro.progmodel.api.IWDAttributeInfo;
import com.sap.tc.webdynpro.progmodel.api.IWDCalculatedAttributeAccessor;
import com.sap.tc.webdynpro.progmodel.api.IWDNode;
import com.sap.tc.webdynpro.progmodel.api.IWDNodeElement;
import com.sap.tc.webdynpro.progmodel.api.WDVisibility;
/**
* Calculated attribute accessors for 'horizontal' tables.
*
* @author Siarhei_Pisarenka
*/
public abstract class HorizontalCalcAttrAccessors {
    /** Abstract accessor. Just stores original 'vertical' node as data source and table column index. */
    private abstract static class AbstractHorizontalAttrAccessor
        implements IWDCalculatedAttributeAccessor {
        final int iCol;
        final IWDNode verticalNode;
        public AbstractHorizontalAttrAccessor(int iCol, IWDNode verticalNode) {
            this.iCol = iCol;
            this.verticalNode = verticalNode;
        }
    };
    /**
     * Abstract horizontal attribute accessor. The accessor shall be setup for an attribute in the 'horizontal'
     * node. It allows to get an access to the particular element of the original 'vertical' node and display
     * the row data in the corresponding table column. The stored column index of the accessor is interpreted
     * at runtime as index of the element if the 'vertical' node.
     *
     * Sub-classes of the accessor shall provide 'vertical' attribute name.
     */
    private abstract static class HorizontalAttrAccessor extends AbstractHorizontalAttrAccessor {
        public HorizontalAttrAccessor(int iCol, IWDNode verticalNode) {
            super(iCol, verticalNode);
        }
        public final void set(IWDNodeElement el, IWDAttributeInfo attr, Object value) {
            verticalNode.getElementAt(iCol).setAttributeValue(getVerticalAttrName(el), value);
        }
        public final Object get(IWDNodeElement el, IWDAttributeInfo attr) {
            return verticalNode.getElementAt(iCol).getAttributeValue(getVerticalAttrName(el));
        }
        protected abstract String getVerticalAttrName(IWDNodeElement hEl);
    };
    /**
     * Horizontal attribute accessor for table columns. The accessor additionally keeps 'vertical' node
     * attribute name. At runtime the accessor redirects any calls to the 'horizontal' table column attribute
     * to the corresponding element of the original 'vertical' node keeping in mind the 'vertical' attribute.
     *
     * The accessor allows to bind column headers, column visibilities and other attributes.
     */
    public static class HorizontalHeaderAttrAccessor extends HorizontalAttrAccessor {
        final String verticalAttr;
        public HorizontalHeaderAttrAccessor(int iCol, IWDNode verticalNode, String verticalAttr) {
            super(iCol, verticalNode);
            this.verticalAttr = verticalAttr;
        }
        protected String getVerticalAttrName(IWDNodeElement hEl) {
            return verticalAttr;
        }
    };
    /**
     * Horizontal attribute accessor for table cells. The accessor extends HorizontalHeaderAttrAccessor and
     * additionally keeps the whole list of 'vertical' node attribute names. Each 'horizontal' row
     * corresponds to the attribute name in the list by row index. At runtime the mapping allows to redirect
     * each 'horizontal' row coming to the accessor to the corresponding 'vertical' attribute.
     *
     * The accessor allows to bind cell editors.
     */
    public static class HorizontalCellAttrAccessor extends HorizontalAttrAccessor {
        final String[] verticalAttrs;
        public HorizontalCellAttrAccessor(int iCol, IWDNode verticalNode, String[] verticalAttrs) {
            super(iCol, verticalNode);
            this.verticalAttrs = verticalAttrs;
        }
        protected String getVerticalAttrName(IWDNodeElement hEl) {
            return verticalAttrs[hEl.index()];
        }
    };
    /**
     * Horizontal attribute getter for table column visibilities. Allows to hide unnecessary table columns
     * that are behind the original 'vertical' node rows. This means that the column is visible only if such row
     * exists in the original 'vertical' node.
     */
    public static class HorizontalColumnVisibilityAttrAccessor
        extends AbstractHorizontalAttrAccessor {
        public HorizontalColumnVisibilityAttrAccessor(int iCol, IWDNode verticalNode) {
            super(iCol, verticalNode);
        }
        public Object get(IWDNodeElement el, IWDAttributeInfo attr) {
            return (iCol < verticalNode.size()) ? WDVisibility.VISIBLE : WDVisibility.NONE;
        }
        public void set(IWDNodeElement arg0, IWDAttributeInfo arg1, Object arg2) {
            throw new UnsupportedOperationException();
        }
    };
}
4 Comments
Labels in this area