Skip to Main Content

Java Development Tools

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Optimize ADF Faces Pages for Printing

Bob Rhubart-OracleMar 19 2015 — edited Apr 16 2015

ADF Faces tables that use percentage values to define the column widths, paired with column stretching set to "multiple", don't display well in print mode. This article outlines a solution to beautify the prints of ADF Faces tables in Oracle JDeveloper 12c, allowing developers to still define table column widths as a percentage. As a prerequisite, this article assumes basic familiarity with common ADF and JavaServer Faces (JSF) technology concepts like task flow and the JSF lifecycle.

by @"Stefanie Schwiesow-Oracle"

Business Case - Distorted Tables in Print Mode

'Veggie Taxi' is a fictive company that delivers boxes with vegetables and fruits to customers based on a prior online order. Every customer can register on the website and place orders for delivery in a certain time interval.

The Veggie Taxi website has been built with Oracle ADF Faces. One of the application views shows a list of deliveries for a certain geographical region and is offered to the courier drivers to plan their route. The delivery addresses of the customers and the delivery time is displayed in an ADF Faces table as shown below.

image001.jpg

Figure 1: "Veggie Taxi" courier schedule page

A button on the page shows a printable view of the page for the courier driver to print the delivery schedule. Pressing the button re-renders the view shown in figure 1 as to a printable view shown in Figure 2.

image002.jpg

Figure 2: Print view of the "Veggie Taxi" courier schedule page

For the print view, the top image is substituted by a text heading to save some ink. Unfortunately, the most important part for the delivery driver, the display of the table data compressed, making it impossible to read.

So, in the following, let's have a look into why this happens, and what the Veggie Taxi development team can do to fix the printable view.

Problem Analysis

The ADF Faces UI component tag <af:showPrintablePageBehavior> is a simple approach available in Oracle AF to render ADF views for printing. While this option works fine for most of the UI components on a page, it struggles for tables that use percentage values to define the column width and use the column stretching set to “multiple”. After some research, the Veggie Taxi ADF development team identified the use of % to determine the column width as the source of the display problem and decided to take a deeper dive into the rendering of such tables to get a hint for a possible solution.

To explain the display behavior it is necessary to understand how ADF normally handles percent defined in the width attribute of columns. When rendered in a browser, the ADF table columns are dynamically sized by client-side Javascript. For column widths that are defined in percent, JavaScript is used to calculate the actual pixel value to add as the width, which happens based on the table width and the visibility of the columns.

The printable output option renders the view optimized for print, which means that Javascript is not executed to evaluate the table column size in pixel. Instead the ADF table renderer takes the percentage values and simply interprets the values as if these were fixed pixel values. The outcome is the table distortion shown in Figure 2.

A feasible solution to this problem is to conditionally set the individual column width based on the output mode the view is rendered in. For example, at runtime Expression Language could be used to apply a fixed width in pixels for pages rendered in print mode and percentage if rendered in normal view mode in a browser (see Tips on using showPrintablePageBehavior with af:table, by Jobinesh Purushothaman).

What sounds like a plan has a shortcoming when the number of table columns changes dynamically at runtime, e.g. through user interaction. If columns are hidden or added to the table at runtime, a fixed width set as the column width does no longer fit the overall table width. To cater to this use case, it's better to code for a generic solution that is implemented in a central location within the application and that then knows how to deal with different outputs at runtime. This is also a less cumbersome approach in cases where there are many tables in an application that define their column width in percent.

To simplify the sample code, this paper makes the assumption that a table stretches to 1000px in print mode and that columns should be resized to fit the while table width.

A First Cut to the Solution

Being confronted with the challenge, the Veggie Taxi development team started a quick brainstorming session to get a first idea for a solution. They eventually came up with a plan that the table size could be calculated and defined in Java before rendering the view. This would be the server-side equivalent of what the client side JavaScript does when running in normal view mode.

If the table columns shall be recalculated programmatically this has to be done BEFORE the table is rendered by the ADF renderer classes otherwise the changed column widths will not be shown in the browser. The last lifecycle stop a request passes when handled by JSF is RENDER_RESPONSE and prepares the user interface of a requested view or fragment for display. Developers can monitor and interact with the JSF request lifecycle using a JSF lifecycle phase listener. As RENDER_RESPONSE is when the view is prepared for render, it's the phase developers need to write code for to handle the printable view case for tables.

A JSF lifecycle phase listener is a java class implementing the javax.faces.event.PhaseListener interface (see http://docs.oracle.com/javaee/6/api/javax/faces/event/PhaseListener.html):

import javax.faces.event.PhaseListener;

public class PrintPhaseListener implements PhaseListener {

}

This class is then registered as phase listener in the faces-config.xml:

<lifecycle>

<phase-listener>view.print.PrintPhaseListener</phase-listener>

</lifecycle>

As mentioned this solution has to hook in before the RENDER RESPONSE phase: So at first the overridden method getPhaseId() of the phase listener needs to be implemented by returning the phase id for the RENDER RESPONSE phase to indicate executing during this phase. As also mentioned before the logic for recalculating the table columns has to be executed before RENDER RESPONSE, so the overridden method beforePhase(PhaseEvent phaseEvent) is the right place for our implementation. Recalculating the column width is furthermore only necessary in print mode and thus this is also considered in the implementation above:

@Override

/\*\*

\* Call method for resizing tables with percentage col widths

*(only in print mode)

\*/

public void beforePhase(PhaseEvent phaseEvent) {

    if (AdfFacesContext.getCurrentInstance().getOutputMode() =

OutputMode.PRINTABLE) {

        TablePrintUtil tableUtil = new TablePrintUtil();

        tableUtil.resizeTables();

    }

}

@Override

public PhaseId getPhaseId() {

    return PhaseId.RENDER\_RESPONSE;

}

The logic for recalculating the table column width is stored in the Java class TablePrintUtil. The complete source code is provided in the sample project you can download for this article.

For tables that uses percentage values to define the column widths, the following tasks needs to be performed:

  • Aggregate the widths of all visible columns
  • Distribute the columns over the assumed width of 1000px in print mode
    • If the table includes columns with fixed width values these should be displayed with their defined width.
    • If the sum of the percentage columns is not exactly 100% this should be considered in distribution.
    • The last column shall be stretched to the remaining width to balance rounding errors

In the provided sample (see that attached file just below this article), you

will find the calculation in the TablePrintUtil.evaluateTableWidth(UIComponent table) method.

During implementing the logic, the "Veggie Taxi" development team found the challenge of how to find tables in an ADF Faces page in the method TablePrintUtil.resizeTables(). The Java Server Faces page component tree must be parsed in a smart way to determine all tables that probably need a recalculation of their columns and call the method evaluateTableWidth for each of these tables. A first approach that might come to mind is to recursively traverse the component tree starting from the view root and execute the method evaluateTableWidth if a RichTable component is detected:

private void resizeTableIterative(UIComponent comp) {

  if (comp == null) { return; }

  if (comp instanceof RichTable) {

          this.evaluateTableWidth(comp, screenWidth);

          return;

  }

  Iterator childrens = comp.getFacetsAndChildren();

  while (childrens.hasNext()) {

        UIComponent child = (UIComponent) childrens.next();

        resizeTableIterative(child);

  }

}

At first glance this approach actually works and leads to a correct layout of the table also in print mode. However, by testing some more complex constellations the development time found out that this approach has some pitfalls:

  • The table is contained in a view in a bounded taskflow and one or more of the columns have an EL (expression language) expression set for the attributes "visible" and/or "rendered" which points to a Managed Bean e.g. in pageflow scope.
  • The table is contained in a declarative component and one or more of the columns have an EL expression set for the attributes "visible" and/or "rendered" which points to the value of an input parameter of the component.

In these constellations the logic of TablePrintUtil tries to evaluate e.g. the rendered attribute of the correspondent column but this EL always evaluate to false in defiance of the actual value behind the EL expression. The effect of this is thatthese columns are assumed to be not visible and are therefore not recalculated by TablePrintUtil: these columns are again displayed compressed in the browser and hence the view is not readable for the user.

To understand the cause of these problems the "Veggie Taxi" development team needed to understand that the components in the component tree are existing in certain contexts. This is especially obvious in the case of bounded taskflows in regions: Every bounded taskflow might have a certain set of managed beans which are only accessible in one instance of this bounded taskflows. A component outside this region cannot access the managed beans of the bounded taskflow executed in the region via EL because it is not executed in the context of this bounded taskflow instance but e.g. in the context of the unbounded taskflow. So this solution has to make sure that the special context for each table component in the component tree is considered correctly during recalculating.

Tune and Optimize the Initial Solution

So again the Veggie Taxi team is in search of a new jigsaw piece and there it is: Starting with JavaServer Faces 1.2 the method invokeOnComponent (FacesContext context, String clientID, ContextCallback callback) is provided, that can be used to avoid the problems regarding context. The third argument in the method call takes a callback method that is executed on the component identified by the clientID passed in as second argument, thus executing the logic in the right context of the component.

Quoting the javadoc, the ContextCallback is "A simple callback interface that enables taking action on a specific UIComponent (either facet or child) in the view while preserving any contextual state for that component instance in the view."

Changing the code to call invokeOnComponent, the sample looks as shown below:

private void resizeTableIterativeInvokeOnComponent(UIComponent comp) {

  if (comp == null) { return; }

  if (comp instanceof RichTable) {

          FacesContext facesContext = FacesContext.getCurrentInstance();

          facesContext.getViewRoot().invokeOnComponent(facesContext,

          comp.getClientId(facesContext),

          new ContextCallback() {

                public void invokeContextCallback(FacesContext

                facesContext, UIComponent target) {

                      evaluateTableWidth(target);

                }

          });

          return;

  }

  Iterator childrens = comp.getFacetsAndChildren();

  while (childrens.hasNext()) {

        UIComponent child = (UIComponent) childrens.next();

        resizeTableIterativeInvokeOnComponent(child);

  }

}

With this change, the solution now works fine with the so far identified problematic cases.

Now That It Works, Make It Fast

Although the solution now functionally works, the ambitions of our development team have been roused and they want to find out if there is even more space for improvement. Yes, there is! JavaServer Faces 2.0 introduced visitTree, a method that excels the component tree traversal, improving the overall performance. This is basically quite similar to the already introduced method invokeOnComponent method because the visitTree method also takes a callback method as parameter. This callback is executed for each component in the component tree. Depending on the return value of the callback method the traversal of the component tree is aborted or continued. Our solution can be thus improved by using this mechanism and by omitting the current iterative approach. Considering controlling the traversal of the component tree there are following possibilities:

  • VisitResult.COMPLETE - stop the traversal all together
  • VisitResult.ACCEPT - continue with the traversal
  • VisitResult.REJECT - stop traversal for the current subtree, but continue with the rest

If the callback is called on a table component there is no need for continuing the traversal in this subtree since no downstreamed table component is expected. If the callback is called on any other component than a table then the traversal of the component tree shall continue downward. In case of any occurring exception the traversal will be stopped immediately. So the code could be changed to the following:

private void resizeTablesVisitTree() {

  FacesContext ctx = FacesContext.getCurrentInstance();

  VisitContext visitContext = VisitContext.createVisitContext(ctx);

  UIXComponent.visitTree(visitContext, ctx.getViewRoot(), new

  VisitCallback() {

          public VisitResult visit(VisitContext context, UIComponent

          component) {

                try {

                      if (component instanceof RichTable) {

                            evaluateTableWidth(component)

                            return VisitResult.REJECT;

                      }

                      return VisitResult.ACCEPT;

              } catch (Exception e) {

                      e.printStackTrace();

                      return VisitResult.COMPLETE;

              }

          }

      });

}

So that's it - our Veggie Taxi development team has finally found a way to help the couriers to be able to finally print a readable view of their delivery schedule and to satisfy the vitamine demand of the "Veggie Taxi" customers.

Summary

The solution introduced in this article shows how to generically hook into the lifecycle and optimize the table layout in print mode for JDeveloper 12c. The sample project was developed based on facelets but the approach will as well work with JSPX pages. This solution can be easily extended to RichTreeTables in an analog way. If you use this solution in your project you might consider these additional adaptations:

  • Adaptation to project CSS-Styles (e.g. consideration of defined margins or paddings during distribution of the columns over the width)
  • Instead of a fixed width of the table in print mode using a dynamic width, e.g. based on the resolution
  • Adaptation to older versions of ADF/JDeveloper is possible but it has to be considered which JSF version is the base: before JSF 2.0 the visitTree-API is not available so probably only the basic solution might be applicable.

About the Author

@"Stefanie Schwiesow-Oracle" is a Senior Principal Consultant at Oracle, with extensive expertise in Oracle SOA Suite, Oracle BPEL, Oracle ADF, Oracle BPM Suite, and other technologies.

Comments

Post Details

Added on Mar 19 2015
0 comments
30,625 views