Monday, April 23, 2012

DWR in JSF? Easy, with PrimeFaces Extensions.

Everybody knows DWR (Direct Web Remoting). DWR enables Java on the server and JavaScript in a browser to interact and call each other. It has several modules for Spring, Struts, Guice, etc. integration. There is also an JSF integration, but it's not fit enough to be used in JSF 2. It requires an expensive XML configuration or a configuration via annotations (@RemoteProxy, @RemoteMethod). The configuration describes a mapping between Java methods and JavaScript functions. JSF 2 standard lacks the ability to call arbitrary methods on JSF managed beans as well. There are some attempts to push up this intention and intergrate this technique in JSF 2, but nothing happend until now. PrimeFaces component library has a component RemoteCommand. Unfortunately, but it doesn't allow to call any Java method with any parameters. Passing parameters is not comfortable with this implementation. They have to be passed as
// call a JavaScript function bound to RemoteCommand with parameters
myFunction({param1: 'value1', param2: 'value2'});
and extracted from the current request on the server side. No JSF converters, validators can be applied, etc. Fortunately we have PrimeFaces Extensions project :-) which brings an own implementation of RemoteCommand. It covers two important use cases.

1) Assign JavaScript parameters to a property in a bean. It can be done in a convenient way with pe:remoteCommand and pe:assignableParam. AssignableParam specifies a name of the parameter and an EL expression for a corresponding Java method (setter) the parameter will be applied to. It allows to attach converters as well. The code below shows how it works.
<p:growl id="growl" showDetail="true" />
        
<pe:remoteCommand id="applyDataCommand" name="applyData" process="@this" update="subject date circle growl"
                  actionListener="#{remoteCommandController.parametersAssigned}">
    <pe:assignableParam name="subject" assignTo="#{remoteCommandController.subject}"/>
    <pe:assignableParam name="date" assignTo="#{remoteCommandController.date}">
        <f:convertDateTime type="both" dateStyle="short" locale="en"/>
    </pe:assignableParam>
    <pe:assignableParam name="circle" assignTo="#{remoteCommandController.circle}">
        <pe:convertJson />
    </pe:assignableParam>
</pe:remoteCommand>
   
<script type="text/javascript">
    circle = {
        radius: 50,
        backgroundColor: "#FF0000",
        borderColor: "#DDDDDD",
        scaleFactor: 1.2
    };
    circle2 = {
     ...
    };
</script>

<h:outputLabel for="subject" value="Subject: " />
<h:outputText id="subject" value="#{remoteCommandController.subject}" />

<h:outputLabel for="date" value="Date: " />
<h:outputText id="date" value="#{remoteCommandController.date}" />  

<h:outputLabel for="circle" value="Circle: " />
<h:outputText id="circle" value="#{remoteCommandController.circle.radius} - 
                 #{remoteCommandController.circle.backgroundColor} - 
                 #{remoteCommandController.circle.borderColor} - 
                 #{remoteCommandController.circle.scaleFactor}" />  

<p:commandButton value="Apply Data" type="button"
        onclick="applyData('hello world', '5/14/07 12:55:42 PM', JSON.stringify(circle))" />
<p:commandButton value="Apply Second Data" type="button"
        onclick="applyData('hello user', '7/11/01 11:55:42 PM', JSON.stringify(circle2))" />
You see that three parameters are passed to the JavaScript function applyData which is bound to the pe:remoteCommand via name attribute. Every pe:assignableParam takes a parameter and assign to the corresponding bean property via assignTo attribute. You can also see that an attached date converter converts date string to a date object (java.util.Date) and an JSON converter (from the PF Extensions project) converts a JavaScript object (circle) to a model Java class (Circle.java). The bean (controller class) and the Circle.java (model class) look simple:
@ManagedBean  
@RequestScoped  
public class RemoteCommandController {  
  
    private String subject;
    private Date date;
    private Circle circle;
 
    // getter, setter
  
    public void parametersAssigned() {  
        FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "ActionListener called", "Parameters assigned");  
        FacesContext.getCurrentInstance().addMessage(null, msg);  
    }
}

public class Circle implements Serializable {  
  
    private int radius;
    private String backgroundColor;
    private String borderColor;
    private double scaleFactor;
  
    // getter, setter
}
Picture:

2) Call a remote Java method with any parameters from JavaScript. It can be done with pe:remoteCommand and pe:methodParam, pe:methodSignature. This is a DWR like approach with zero configuration. MethodParam specifies a name of the parameter and MethodSignature specifies a comma or space separated list with full qualified class names. Class names should match passed parameters in the same order as they were defined. The code below shows how it works.
<p:growl id="growl" showDetail="true" />

<pe:remoteCommand id="applyDataCommand" name="applyData" process="@this" update="growl"
                  actionListener="#{remoteCommandController.printMethodParams}">
    <pe:methodSignature parameters="java.lang.String, java.util.Date, org.primefaces.extensions.showcase.model.Circle" /> 
    <pe:methodParam name="subject"/>
    <pe:methodParam name="date">
        <f:convertDateTime type="both" dateStyle="short" locale="en"/>
    </pe:methodParam>
    <pe:methodParam name="circle">
        <pe:convertJson />
    </pe:methodParam>
</pe:remoteCommand>
   
<script type="text/javascript">
    circle = {
        radius: 50,
        backgroundColor: "#FF0000",
        borderColor: "#DDDDDD",
        scaleFactor: 1.2
    };
    circle2 = {
     ...
    };
</script>

<p:commandButton value="Apply Data" type="button"
        onclick="applyData('hello world', '5/14/07 12:55:42 PM', JSON.stringify(circle))" />
<p:commandButton value="Apply Second Data" type="button"
        onclick="applyData('hello user', '7/11/01 11:55:42 PM', JSON.stringify(circle2))" />
The bean method printMethodParams (defined as actionListener) will be called when user pushes one of the both command button. The method generates a message which is shown as growl popup.
@ManagedBean  
@RequestScoped  
public class RemoteCommandController {  
  
    public void printMethodParams(String subject, Date date, Circle circle) {  
        FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "ActionListener called",  
                  "Subject: " + subject + ", Date: " + date + ", Circle - backgroundColor: " + circle.getBackgroundColor());  
  
        FacesContext.getCurrentInstance().addMessage(null, msg);  
    }
}
Converters are used as well. They can be also defined as usual in JSF by converter attribute in pe:assignableParam / pe:methodParam.

Described features are available in the PF Extensions 0.5-SNAPSHOT.

Edit: DWR allows a reverse call from Java to JavaScript as well. It works bidirectional. Well, it's possible with Atmosphere integration or PrimeFaces Push too. Furthermore, PrimeFaces provides a server side way to execute JavaScript when the ajax request completes. Example: RequestContext.getCurrentInstance().execute("dialog.hide()");

5 comments:

  1. Hello, How do I get in touch with you? There is no email or contact info listed .. please advise .. thanks .. Mary. Please contact me maryregency at gmail dot com

    ReplyDelete
  2. There is an e-mail in my profile, I think. What do you want to tell me? Regards.

    ReplyDelete
  3. Nice post! It really helped me out with p:remoteCommand, I'll be downloading this extension, Regards.

    ReplyDelete
  4. Hi, is there any way to put a validator too??? like the coverter option?

    ReplyDelete
  5. Good question. Please ask in the forum http://forum.primefaces.org/viewforum.php?f=14.

    ReplyDelete

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