Wednesday, January 19, 2011

ADF printable pages : Fetching more data / rows for displaying a printable page

The Problem
When we use the built-in features to render printable pages, we are not making any additional server hits, say, to fetch all rows in a table that is to be included in the printable version of a page. In some cases we may want to have an entirely different layout for the printable version of the page, with additional disclaimer text and other elements.
The Solution
Use an alternative OutputMode to programmatically cause the printable page behavior.
The <af:showPrintablePageBehavior/>does not handle our requirement to hit the server for additional data, the alternative approach would be (depending on your scenario) to use a duplicate page with "email" features that mimics the features of the original page, but with layout that is more printer friendly. Enabling the email features renders the pages exactly as the <af:showPrintablePageBehavior/> would. To do this, you can pretty much copy paste your code as another page(You'll need the pageDef and other related files too), and if you are having a bounded taskflow with jsffs, then the printable page should be a view activity in the unbounded taskflow that can is marked as "Bookmarkable" with parameters,  so that the page can take the parameters and load all the data necessary. In the printable page, make sure that the 
  • data table content delivery is set to immediate and 
  • the AutoHeightRows is set to the FetchSize and 
  • the FetchSize is -1 so that all rows will be populated in to the VO that is backing the table.
This way you don't have to tweak the VO itself, but force to fetch all rows for the printable version of the page. Its definitely a performance hit if you are printing huge tables, in which case exporting the data as excel/html may be a more attractive solution.

Once the new "Printable" page is designed, running it in isolation may look fine, but when printing you wil notice that a true printable page behaviour is not produced. eg.: On screen, a table may display 50 rows, but on the paper, it may get truncated at 10. The new page is designed to hit the server to get all the data that we need to display in the printable version of a page. To make it behave like a page produced by the <af:showPrintablePageBehavior/>, we change the OutputMode mark the page as "Email-able" which renders the page in look and behavior like <af:showPrintablePageBehavior/>
You notify the ADF Faces framework to convert your page to be email/printer friendly by appending the following the request parameter to the URL for the page:
org.apache.myfaces.trinidad.agent.email=true
This causes the page to be rendered as a printable page
You can launch the page in  another window by setting the useWindow attribute on the command component to launch a new window/tab in the browser with the printable page. When launching the Printable page, you can include the URL parameters to load the data along with the org.apache.myfaces.trinidad.agent.email parameter.

This works well when you all the details on the printable page can be fetched with a few parameters. The most common example is when you have a master-detail or search/result type page navigation where you have one page that displays a number of records and you choose one record to view in deatil and you need to print the detailed information of that one entity/record (the details page). In this case, the details/Printable page can take one ID chosen from the master/parent/search page and then fetch all related information using that ID in the second page. If however the situation is when a search page is to be made printable, the usual case is that the use wants to save the results from his/her search and exporting the content as excel/html works better.

Thursday, January 13, 2011

ADF query component ( <af:query> ) with a loading screen / spinner pop-up (Updated)


New updated alternative approach here 
The problem
I have an <af:query> component that displays query results in an <af:table> on my page. When the user hits the search button, I need to display loading screen that displays a loading animation and blocks user input.


The solution
Use  a pop-up to display a spinner while the query executes.


Before you proceed with implementing this approach,  there is another much much simpler possible solution. If displaying an animation to indicate that the query is being processed is all you need, then <af:statusIndicator> may be an option. It is extremely simple to use : Just drop the tag on to when you want the spinner to appear and you’re done. This however, does not prevent user input during the query execution and the spinner will be activated on all server calls. The pop-up approach detailed below is more complex but allows much more control over the whole process.

The rough idea is to :
  1. Intercept the Query/Search action in a backing bean.
  2. Launch a pop-up blocking user input and showing a loading message/spinner
  3. Fire the actual query to the database and close pop-up when query is done.

The finer detail is a bit more involved :
  1. Intercept the Query/Search action
Implement a query listener for the <af:query> component in your backing bean.
<af:query id="qryId1" 
          headerText="Search" disclosed="true"
          value="#{bindings.EmployeeSearchQuery.queryDescriptor}"
          model="#{bindings.EmployeeSearchQuery.queryModel}"                      
          resultComponentId="::resId1"
          queryListener="#{pageFlowScope.testBean.processQuery}"/>
public void processQuery(QueryEvent queryEvent)
  {
    setQEvent(queryEvent); //Save the query event for the method that really fires the query to use.
    toggleBusyPopup(true); //Fires a popup, which inturn fires a serverListener.
  }

  1. Launch the pop-up from the backing bean with Javascript :
public void toggleBusyPopup(boolean isShown){
    FacesContext context = FacesContext.getCurrentInstance();
    RichPopup popup = (RichPopup) JSFUtils.findComponent("busyPopup");
    ExtendedRenderKitService service =
      Service.getRenderKitService(context, ExtendedRenderKitService.class);
    if (isShown){
      service.addScript(context,
                        "var popup = AdfPage.PAGE.findComponent(\"" +
                        popup.getClientId(context) + "\"); popup.show();");
    }
    else{
      service.addScript(context,
                        "var popup = AdfPage.PAGE.findComponent(\"" +
                        popup.getClientId(context) + "\"); popup.hide();");
    }
    return;
  }

  1. Fire the actual query to the database and close pop-up when query is done
You need to fire the query and keep the pop-up open as long as the query executes.
  1. To fire the query, from within the pop-up, use an <af:clientListener> to trigger a Javascript function on the page.
  2. ...
    <af:popup id="busyPopup" clientComponent="true">
              <af:clientListener method="onPopupOpened" type="popupOpened"/>
    ...
    
  3. This Javascript method will in turn  trigger an <af:serverListener> inside the pop-up that will execute a method on the backing bean.
  4. <af:resource type="javascript">
            function onPopupOpened(event)
            {
              AdfCustomEvent.queue(event.getSource(), "popupOpenListner", 
              { }, false);
            }      
    </af:resource>
    
    <af:popup id="busyPopup" clientComponent="true">
              <af:clientListener method="onPopupOpened" type="popupOpened"/>
              <af:serverListener type="popupOpenListner"
                                 method="#{pageFlowScope.testBean.processSearchQuery}"/>
    
  5. The backing bean method will fire the actual query
  6. When the query is done, the same backing bean method will update the result table and hide the loading screen popup.
  7. public void processSearchQuery(ClientEvent clientEvent)
      {
        // Do query : //
        invokeMethodExpression("#{bindings.EmployeeSearchQuery.processQuery}",
                               Object.class, QueryEvent.class, getQEvent());
        AdfFacesContext.getCurrentInstance().addPartialTarget(JSFUtils.findComponent("resId1"));
        toggleBusyPopup(false);
      }
    



Running the example application.

You can download the application from here.
To run it, unzip the files and open the project in Jdeveloper.
Run the test.jspx by navigating to  ViewController > Web Content > test.jspx Right-Click > Run