Sunday, January 25, 2015

PrimeFaces: Opening external pages in dynamically generated dialog

I already blogged about one recipe in the upcomming 2. edition of the PrimeFaces Cookbook. In this post, I would like to post the second recipe about a small framework called Dialog Framework. I personally like it because I remember my costly effort to do the same thing with the Struts Framework. When you wanted to load an external page into a popup and submit some data to this page, you had to call window.open with an hidden form, set passed values into hidden fields, submit the form to the external page via JavaScript and wait until the page is ready to use in window.onload or document.ready. A lot of manuelly work. PrimeFaces does this job for you and, in addition, provides with p:dialog a beautiful user interface as replacement for popup.

The regular usage of PrimeFaces' dialog is a declarative approach with p:dialog. Beside this declarative approach, there is a programmatic approach as well. The programmatic approach is based on a programmatic API where dialogs are created and destroyed at runtime. It is called Dialog Framework. The Dialog Framework is used to open external pages in dynamically generated dialog. The usage is quite simple, RequestContext provide two methods: openDialog and closeDialog that allow opening and closing dynamic dialogs. Furthermore, the Dialog Framework makes possible to pass data back from the page displayed in the dialog to the caller page.

In this recipe, we will demonstrate all features available in the Dialog Framework. We will open a dialog with options programmatically and pass parameters to the page displayed in this dialog. We will also meet the possibility for communicating between the source (caller) page and the dialog.

Getting ready

Dialog Framework requires the following configuration in faces-config.xml:
<application>
    <action-listener>org.primefaces.application.DialogActionListener</action-listener>
    <navigation-handler>org.primefaces.application.DialogNavigationHandler</navigation-handler>
    <view-handler>org.primefaces.application.DialogViewHandler</view-handler>
</application>

How to do it...

We will develop a page with radio buttons to select one available PrimeFaces' book for rating. The rating itself happens in a dialog after a click on the button Rate the selected book.

The XHTML snippet to the screenshot is listed below.
<p:messages id="messages" showSummary="true" showDetail="false"/>

<p:selectOneRadio id="books" layout="pageDirection" value="#{dialogFrameworkBean.bookName}">
    <f:selectItem itemLabel="PrimeFaces Cookbook" itemValue="PrimeFaces Cookbook"/>
    <f:selectItem itemLabel="PrimeFaces Starter" itemValue="PrimeFaces Starter"/>
    <f:selectItem itemLabel="PrimeFaces Beginner's Guide" itemValue="PrimeFaces Beginner's Guide"/>
    <f:selectItem itemLabel="PrimeFaces Blueprints" itemValue="PrimeFaces Blueprints"/>
</p:selectOneRadio>

<p:commandButton value="Rate the selected book"
            process="@this books"
            actionListener="#{dialogFrameworkBean.showRatingDialog}"
            style="margin-top: 15px">
    <p:ajax event="dialogReturn" update="messages" listener="#{dialogFrameworkBean.onDialogReturn}"/>
</p:commandButton>
The page in the dialog is a full page bookRating.xhtml with a Rating component p:rating. It also shows the name of the book selected for rating.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html" locale="en">
    <f:metadata>
        <f:viewParam name="bookName" value="#{bookRatingBean.bookName}"/>
    </f:metadata>
    <h:head>
        <title>Rate the book!</title>
    </h:head>
    <h:body>
        <h:form>
            What is your rating for the book <strong>#{bookRatingBean.bookName}</strong>?

            <p/>

            <p:rating id="rating">
                <p:ajax event="rate" listener="#{bookRatingBean.onrate}"/>
                <p:ajax event="cancel" listener="#{bookRatingBean.oncancel}"/>
            </p:rating>
        </h:form>
    </h:body>
</f:view>
</html>
The next screenshot demonstrates how the dialog looks like.


A click on a rating star or the cancel symbol closes the dialog. The source (caller) page displays a message with the selected rating value in the range from 0 till 5.

The most interesting part is the logic in beans. The bean DialogFrameworkBean opens the rating page within the dialog by invoking the method openDialog() with the outcome, options and POST parameters on a RequestContext instance. Furthermore, the bean defines an AJAX listener onDialogReturn() which is invoked when the data (selected rating) is returned from the dialog after it was closed.
@Named
@ViewScoped
public class DialogFrameworkBean implements Serializable {

    private String bookName;

    public void showRatingDialog() {
        Map<String, Object> options = new HashMap<String, Object>();
        options.put("modal", true);
        options.put("draggable", false);
        options.put("resizable", false);
        options.put("contentWidth", 500);
        options.put("contentHeight", 100);
        options.put("includeViewParams", true);

        Map<String, List<String>> params = new HashMap<String, List<String>>();
        List<String> values = new ArrayList<String>();
        values.add(bookName);
        params.put("bookName", values);

        RequestContext.getCurrentInstance().openDialog("/views/chapter11/bookRating", options, params);
    }

    public void onDialogReturn(SelectEvent event) {
        Object rating = event.getObject();
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "You rated the book with " + rating, null);

        FacesContext.getCurrentInstance().addMessage(null, message);
    }

    // getters / setters
    ...
}
The bean BookRatingBean defines two listeners for the Rating component. They are invoked when the user clicks on a star and the cancel symbol respectively. We call there closeDialog() on a RequestContext instance to trigger the dialog closing and to pass the current rating value to the mentioned listener onDialogReturn().
@Named
@RequestScoped
public class BookRatingBean {

    private String bookName;

    public void onrate(RateEvent rateEvent) {
        RequestContext.getCurrentInstance().closeDialog(rateEvent.getRating());
    }

    public void oncancel() {
        RequestContext.getCurrentInstance().closeDialog(0);
    }

    // getters / setters
    ...
}

How it works...

The RequestContext provides two methods of the same name openDialog to open a dialog dynamically at runtime. The first one only has one parameter - the logical outcome used to resolve a navigation case. The second one has three parameters - outcome, dialog's configuration options and parameters that are sent to the view displayed in the dialog. We used the second variant in the example. The options are put into a Map as key, value pairs. The parameters are put into a Map too. In our case, we put the name of the selected book. After that, the name is received in the dialog's page bookRating.xhtml via the f:viewParam. f:viewParam sets the transferred parameter into the BookRatingBean, so that it is available in the heading above the Rating component. Tip: Please refer the PrimeFaces User's Guide to see a full list of supported dialog's configuration options.

Let us go through the request-response life cycle. Once the response is received from the request caused by the command button, a dialog gets created with an iframe inside. The URL of the iframe points to the full page, in our case bookRating.xhtml. The page will be streamed down and shown in the dialog. As you can see, there are always two requests: the first initial POST and the second GET sending by iframe. Note that the Dialog Framework only works with initial AJAX requests. Non-AJAX request are ignored. Please also note the the title of the dialog is taken from the HTML title element.

As we already mentioned above, the dialog can be closed programmatically by invoking the method closeDialog on a RequestContext instance. On the caller page, the button that triggers the dialog needs to have an AJAX listener for the dialogReturn event to be able to receive any data from the dialog. The data is passed as parameter to the method closeDialog(Object data). In the example, we pass either a positive integer value rateEvent.getRating() or 0.

2 comments:

  1. I would solve this without dialog framework and use a simple dialog with a composite component instead of a separate view. The rating is bound to a brean property and only a dummy method that renders the FacesMessage after rating would be needed.

    Can dialog framework render sites from other domains?, else I still need an example that can not be solved with a basic dialog.

    ReplyDelete

Note: Only a member of this blog may post a comment.