Skip to end of metadata
Go to start of metadata

Summary

How to download data from a table using the on-demand resource streaming technique in NW 2004s combined with a ToolBarLinkToURL-UI-Element to open the Excel resource instantly. This technique does not require opening a Web Dynpro popup window with a LinkToURL-UI-element pointing to a cached Excel resource. This sample application is implemented in two separate components: a reusable service component for Excel export and a client component displaying table data.


Applies To: Web Dynpro for Java UI Development, SAP NetWeaver 7.0
Code Sample: TutWD_ExcelExport_NW04s.zip - WD Sample Project ZIP for this Code Tutorial based on NW04s SP Stack 8
Level of complexity: Intermediate
Time required for completion: 1.5 hrs.
Created by: Bertram Ganz, SAP AG
Submitted: April 23, 2007

Introduction

This code tutorial demonstrates how to implement the following functionality in a Web Dynpro Java application based on NW 04s:

  • how to export table data to a MIME resource (Excel file) on-demand
  • how to link to such an on-demand resource in a table tool bar using a ToolbarLinkToURL-UI-element

The following screenshots demonstrate the required user interaction:

  1. user clicks ToolBarLinkToURL-UI-element to download table data exported to an Excel file.
  2. a popup window occurs which asks the user how to handle the downloaded resource: save or display.
  3. the mime file or Excel resource is opened with the related progam installed on client side (MS Excel).

In comparison to the Excel Export Sample Application for NW 04 the NW 04s solution described in this code tutorial does not require to open a Web Dynpro popup window any more. This can be achieved by streaming the Excel resource content on-demand although the resource object is initially being created. This empty resource object is called 0-byte-resource. We can initially link to the URL of this resource using a ToolbarLinkToURL-UI-element in the table tool bar without knowing its binary content yet.

Related Sample Project

You can download the complete sample project implementing this Web Dynpro code tutorial here.


Creating a Reusable Excel Export Component

The first part of this code tutorial describes how to create a faceless Web Dynpro component encapsulating the Excel export functionality.

To use this Excel export component (service component) in a client component displaying table data the following steps are required:

  1. Client component must invoke the initialize()-method of the service component passing references to the table's data node and to the table column's header texts.
  2. Client component must bind the property reference of its ToolbarLinkToURL-UI-element to the attribute ResourceURL in the interface context of the service component (implies context mapping). The Excel export component calculates this URL and exposes it to the client component.
  3. Client component must bind the property imageSourcee of its ToolbarLinkToURL-UI-element to the attribute ImageURL in the interface context of the service component (implies context mapping). The Excel export component calculates this image URL and exposes it to the client component.

Defining the Component Controller Context

The context of the component controller looks like this ...

Name

Element

Properties

Excel

node

cardinality = 1..1

  • IconURL

attribute

type = string

  • Resource

attribute

type = Resource

  • ResourceOnDemandStreamCalc

calculated attribute

type = IWDInputStream

  • ResourceURL

attribute

type = String

The two important attributes are Resource and ResourceOnDemandStreamCalc. They are used as a combination for streaming resource content to the client on-demand. The other two attributes are used for exposing the resource URL and the resource icon (Excel icon) URL to the client component using the service component for exporting table data as Excel file.

Implementing the Component Controller

Initializing Member Variables
First we declare and initialize some private member variables in the component controller class:

...
  //@@begin others
  // ======= Private Member Variables =================
  private IWDResource excel0ByteResource = null;
  private IPublicExcelExportComp.IExcelElement excelResourceElement = null;
  private IWDAttributePointer calcAttrPointer = null;
  private String calcAttrName = IPublicExcelExportComp.IExcelElement.RESOURCE_ON_DEM_STREAM_CALC;
  private IWDNode tableDataNode = null;
  private Map tableColumnInfos = null;
  ...
}

Initializing the Componet Controller in wdDoInit()

In the wdDoInit()-hook method of the component controller we initialize the member variables excelResourceElement and calcAttrPointer and then generate the URL of the Excel image icon which is deployed with the Excel export component.

//@@begin javadoc:wdDoInit()
  /** Hook method called to initialize controller. */
  //@@end
  public void wdDoInit()
  {
    //@@begin wdDoInit()
    excelResourceElement = wdContext.currentExcelElement();
    calcAttrPointer = excelResourceElement.getAttributePointer(calcAttrName);
    // initilize context attribute storing Excel icon URL. The client component
    // can map to this URL in order to display an excel icon with the ToolBarLinkToURL UI element.
    try {
      wdContext.currentExcelElement().setIconURL(
        WDURLGenerator.getWebResourceURL(wdComponentAPI.getDeployableObjectPart(), "excel.gif"));
    } catch (WDURLException e) {
      wdComponentAPI.getMessageManager().reportException(e.getLocalizedMessage(), true);
    }
    //@@end
  }

Implementing Private Method initializeOnDemandExcelResource()

This private method is invoked in the calculated context attribute getter method where the on-demand resource content is actually being created and in the initialize()-method which is invoked by the client component. We create a new and empty 0-byte resource object, store it in the component controller context, get its URL and store this URL in the context too.

...
  //@@begin others
  ...
  //======= Private Methods ===========================
  /**
   * Create new on-demand resource instance for excel file. This method must be
   * invoked initially and in the calculated context attribute getter method (calculating)
   * the excel resource input stream. Otherwise the resource is streamed to the client only
   * once but not afterwards so that later updates of the table data are not reflected in the
   * streamed Excel file.
   * URL is stored in context so context in client component can map to it.
   */
  private void initializeOnDemandExcelResource() {
    excel0ByteResource =
      WDResourceFactory.createResource(calcAttrPointer, "Table.xls", WDWebResourceType.XLS);
    excelResourceElement.setResource(excel0ByteResource);
    excelResourceElement.setResourceURL(
      excel0ByteResource.getUrl(WDFileDownloadBehaviour.OPEN_INPLACE.ordinal()));
  }
  ...

Implementing Public Method initialize()

The initialize()-method is invoked by the client component passing references to the table's dataNode and to its column information (display texts of the table column headers). This method delegates to the initializeOnDemandExcelResource()-method where a new 0-byte resource object is stored in the component context.

//@@begin javadoc:initialize()
  /** Declared method. */
  //@@end
  public void initialize( com.sap.tc.webdynpro.progmodel.api.IWDNode dataNode, java.util.Map columnInfos )
  {
    //@@begin initialize()
    this.tableDataNode = dataNode;
    this.tableColumnInfos = columnInfos;
    // Initialize context attribute storing on-demand Excel resource
    initializeOnDemandExcelResource();
    //@@end
  }

Implementing the calculated context attribute getter getExcelResourceOnDemStreamCalc()

To stream the content of the Excel resource to the client on-demand we implement a calculated context attribute getter method:

//@@begin javadoc:getExcelResourceOnDemStreamCalc(IPrivateExcelExportComp.IExcelElement)
  /**
   *  Declared getter method for attribute ResourceOnDemStreamCalc of node ExcelResource
   *  @param element the element requested for the value
   *  @return the calculated value for attribute ResourceOnDemStreamCalc
   */
  //@@end
  public com.sap.tc.webdynpro.progmodel.api.IWDInputStream getExcelResourceOnDemStreamCalc(IPrivateExcelExportComp.IExcelElement element)
  {
    //@@begin getExcelResourceOnDemStreamCalc(IPrivateExcelExportComp.IExcelElement)
    IWDInputStream excelOnDemandStream = null;
    try {
      excelOnDemandStream =
        WDResourceFactory.createInputStream(toExcel(tableDataNode, tableColumnInfos).getBytes("UTF-8"));
    } catch (UnsupportedEncodingException e) {
      wdComponentAPI.getMessageManager().reportException(e.getLocalizedMessage(), true);
    }

    // Reset context attribute 'Excel.Resource' again by creating a new 0-byte resource object.
    // Otherwise the on-demand Excel stream gets calculated only once as the resource object,
    // cached in the Web Dynpro Binary Cache on server-side, keeps the same.
    initializeOnDemandExcelResource();

    // Return excel on-demand input stream
    return excelOnDemandStream;
    //@@end
  }

Implementing the Excel Export Code

The following lines of code are implemented in the ExcelExport component controller's private user coding area:

//@@begin others

  // ======= Private Member Variables =================
  ...

  //======= Private Methods ===========================

  /**
   * Create new on-demand resource instance for excel file. ...
   */
  private void initializeOnDemandExcelResource() {
   ...
  }

  /**
   * Create an XML respresentation of the given context data.
   * RESTRICTION: Excel 2003 must be installed on the client machine for opening the given xml data
   * representation as an Excel file.
   * @return string respresentation of the created xml data for Excel 2003.
   */
  private String toExcel(IWDNode dataNode, Map columnInfos) {
    StringBuffer x = new StringBuffer();
    String entriesName = dataNode.getNodeInfo().getName();
    String entryName = entriesName + "Element";

    // trim given header texts, so that XML element names adhere to the rule 'no spaces contained'.
    trimHeaderTexts(columnInfos);

    x.append("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    x.append("<").append(entriesName).append(">\n");

    for (int i = 0; i < dataNode.size(); ++i) {
      IWDNodeElement dataNodeElement = dataNode.getElementAt(i);
      x.append("<").append(entryName).append(">\n");

      for (Iterator iter = columnInfos.entrySet().iterator(); iter.hasNext();) {
        Map.Entry entry = (Map.Entry) iter.next();
        String attributeName = (String) entry.getKey();
        String headerName = (String) entry.getValue();
        x
          .append("<")
          .append(headerName)
          .append(">")
          .append("<![CDATA[")
          .append(dataNodeElement.getAttributeValue(attributeName))
          .append("]]>")
          .append("</")
          .append(headerName)
          .append(">\n");
      }
      x.append("</").append(entryName).append(">\n");
    }
    x.append("</").append(entriesName).append(">\n");
    return x.toString();
  }

  /**
   * Trims all given header texts because XML element names (to be created) must adhere to XML
   * rules (that is, they cannot include special characters, spaces, and so on.
   */
  private void trimHeaderTexts(Map columnInfos) {
    for (Iterator iter = columnInfos.entrySet().iterator(); iter.hasNext();) {
      Map.Entry entry = (Map.Entry) iter.next();
      String trimmedHeaderText = trimHeaderText((String) entry.getValue());
      entry.setValue(trimmedHeaderText);
    }
  }

  /**
   * Trims one single header text. Omit empty strings. Convert sub-strings to lower case.
   * Convert first char of sub-strings to upper case.
   * Example: "Product Price in EURO" ->"ProductPriceInEuro"
   */
  private String trimHeaderText(String headerText) {
    StringBuffer newHeaderText = new StringBuffer();
    String token;
    StringTokenizer tokenizer = new StringTokenizer(headerText.trim());
    while (tokenizer.hasMoreTokens()) {
      token = tokenizer.nextToken();
      newHeaderText.append(token.substring(0, 1).toUpperCase());
      newHeaderText.append(token.substring(1).toLowerCase());
    }
    return newHeaderText.toString();
  }
  //@@end

Mapping the Component Interface Controller Context

To expose the context attributes IconURL and ResourceURL to the client component we must add mapped attributes to the interface context of the Excel export component:

Implementing the Component Interface Controller

Finally we declare and implement the public method initialize() to the interface controller of the Excel export component. This method is invoked by the client component and delegates to the same method in the component controller.

//@@begin javadoc:initialize()
  /** Declared method. */
  //@@end
  public void initialize( com.sap.tc.webdynpro.progmodel.api.IWDNode dataNode, java.util.Map columnInfos )
  {
    //@@begin initialize()
    wdThis.wdGetExcelExportCompController().initialize(dataNode, columnInfos);
    //@@end
  }

Using the ExcelExport Component inside the Table Component

Using the Excel export component in the client component (table display component) is quite simple. First we add a component usage relation to the ExcelExport component in the table component.

Mapping the View Controller Context to the Interface Context of the ExcelExport Component

In the next step we define a context mapping relation from the TableDisplayView controller to the Interface Controller of the used ExcelExport service component. This can be defined in the data modeler of the Web Dynpro Tools easily:

Adding a ToolBarLinkToURL UI Element to the View Layout

After mapping the two URL attributes in teh view context to the interface context of the used ExcelExport component we can bind the corresponding properties in a new ToolBarLinkToURL-UI-element to them. At runtime the transfer of the URL values from the ExcelExport service component to the TableDisplay client component is fully automated based on data binding and context mapping.

Implementing the View Controller

Finally we implement one single line of custom code within the wdDoInit()-hook-method of the TableDisplayView view controller class. We just invoke the initialize()-method exposed by the IExternal-API of the ExcelExport component interface controller.

The required controller usage relation is already added to the view controller properties based on the context mapping definition done in the data modeler.

//@@begin javadoc:wdDoInit()
  /** Hook method called to initialize controller. */
  //@@end
  public void wdDoInit()
  {
    //@@begin wdDoInit()
    ...
    // initialize reusable Excel Export component by passing pointers
    // to the table's data node and to its column infos
    wdThis.wdGetExcelExportCompInterface().initialize(wdContext.nodeProducts(), getProductColumnInfos());
    //@@end
  }

The client component must pass a Map with key-displaytext-pairs for the to the Excel export service component. The keys are the technical context attribute names and the values are the corresponding displaytexts used for column header texts. Please note, that the described Excel export component cannot create column header texts with empty spaces. This means in case a displaytext comprises spacial characters, the excel export component trims them because XML element names (to be created) must adhere to XML rules (that is, they cannot include special characters, spaces, and so on. A LinkedHashMap is used to keep a stable order of key-value pairs (context attribute names, column header texts) passed by the client component. 

...
  //@@begin others

  /**
   * Creates a map of key-displaytext-pairs representing table column infos.
   * Keys: context attribute names (type String)
   * Values: corresponding column header texts (type String)
   * @return columnInfosMap
   */
  private Map getProductColumnInfos() {
    Map columnInfosMap = new LinkedHashMap();
    columnInfosMap.put(IPrivateTableCompBasketView.IProductsElement.QUANTITY, "Quantity");
    columnInfosMap.put(IPrivateTableCompBasketView.IProductsElement.ARTICLE, "Article");
    columnInfosMap.put(IPrivateTableCompBasketView.IProductsElement.COLOR, "Color");
    columnInfosMap.put(IPrivateTableCompBasketView.IProductsElement.PRICE, "Price in EURO");
    columnInfosMap.put(
      IPrivateTableCompBasketView.IProductsElement.TOTAL__PER__ARTICLE,
      "Total Per Article In Euro");
    return columnInfosMap;
  }
  //@@end

Further Reading

Read more about tpics covered in this tutorial:

2 Comments

  1. Hi Bertram,

    Allow me to make a few minor remarks on the "Uploading and Downloading Files in Web Dynpro Java - NW04s" tutorial.

    The getFileSize method of FileUploadView uses resource.read(false). This indeed works in the tutorial because the uploaded file is never read, but I assume in a real-life situation it's pretty useless to upload a file and ignore it's content. The getFileSize method also uses stream.available() to determine the size of the file. Is this guaranteed to work in the context of file uploads? It really depends on the underlying implementation of java.io.InputStream, but available() usually doesn't return the total size of the input stream, especially not when reading a file over a network. Even the implementation of available() of java.io.FileInputStream is a native method.

    As a sidenote, the onActionUpload method of FileUploadView.java uses resource.getResourceType().getFileExtension() to determine and print out the file extension of the uploaded file. I noticed that, after uploading a file, the WDWebResourceType object that's created is based on the MIME type. Uploading an XML file, i.e. a file having MIME type text/xml and regardless of the file extension, always results in the WDWebResourceType.XDP instance and hence the file extension is always xdp. This is because in WDWebResourceType's implementation, the WDWebResourceType.XDP instance is created after the WDWebResourceType.XML instance, both have the same text/xml MIME type, both are "added to the container", which happens to be an implementation of java.util.Map using the MIME type as a key; adding XDP after XML will actually replace XML with XDP, and the WDWebResourceType is based on the MIME type. (I assume my explanation is more complex than the problem...) Actually WDWebResourceType.XDP was added in some patch of NW04s; it's not present in NWDS NW04s SPS10 patch level 0.

    Kind regards,
    Sigiswald

  2. Unknown User (100pwty6y)

    Hi Beltram,

    with the class

    IWDResource excel0ByteResource

    I have a problem: excel doesn't start

    so I used the class IWDCachedWebResource

    regards,

    Matteo