2

I'm relatively new to JSF and trying to learn how current JSF 2 applications are designed. I've seen reference to single page applications that use ajax. Can someone fill me in on some of the techniques used and / or point me to a model or book? The books I've seen (JSF Complete Reference etc.) are good for basic tech issues but I can't find a source for current design techniques.

Thanks Dave

Dave
  • 545
  • 3
  • 14
  • 29
  • In my opinion, the books dedicated purely to core JSF(and there aren't many) don't do a good enough job on the topic. You'll end up having to combine books to get the sum quality you need. Try [this book](http://www.amazon.com/Beginning-JSP-JSF-Tomcat-Development/dp/1430246235/ref=sr_1_5?ie=UTF8&qid=1351529959&sr=8-5&keywords=apress+jsf) by Guilio Zambon and [this](http://www.amazon.com/Practical-RichFaces-Max-Katz/dp/1430234490/ref=sr_1_4?ie=UTF8&qid=1351529959&sr=8-4&keywords=apress+jsf) by the creators of the Richfaces JSF library. For the rest, hit the web. See http://balusc.blogspot.com – kolossus Oct 29 '12 at 17:10
  • I'll take a look at the Richfaces book, thanks. – Dave Oct 30 '12 at 16:05

1 Answers1

5

In order to implement your Single Page Application, you should state which piece of your page should be rendered. This can be accomplished making use of a boolean flag such as create, edit, list, and so on. For instance, see the following (Just relevant code)

<h:body>
    <h:form rendered="#{userController.stateManager.create}">
        <h:panelGroup rendered="#{not empty facesContext.messageList or userController.stateManager.failure}">
            <!--render error message right here-->
        </h:panelGroup>
        <div>
            <label>#{messages['br.com.spa.domain.model.User.name']}</label>
            <h:inputText value="#{user.name}"/>
        </div>
        <h:commandButton action="#{userController.create}">
             <f:ajax execute="@form" render="@all"/>
             <f:actionListener type="br.com.spa.web.faces.listener.StateManagerActionListener" />
             <f:setPropertyActionListener target="#{userController.stateManager.create}" value="true"/>
             <f:setPropertyActionListener target="#{userController.user}" value="#{user}" />
        </h:commandButton>
    </form>
</h:body>

Notice that our form will be rendered when a flag create is true - See second line above. To wrap our flags, we create a classe named StateManager as follows

/**
  * I am using lombok, which takes care of generating our getters and setters. For more info, please refer http://projectlombok.org/features/index.html
  */
@Setter @Getter
public class StateManager {

    private boolean create;
    private boolean edit;
    private boolean list;

}

Now, because we are using only a single page, we should use a ViewScoped managed bean, which keep our managed bean scoped active as long as you are on the same view - Is it a single page application, right ? So, no navigation. With this in mind, let's create our managed bean.

@ManagedBean
@ViewScoped
public class UserController implements StateManagerAwareManagedBean {

    private @Inject UserService service;

    private @Getter @Setter stateManager = new StateManager();

    private @Getter @Setter List<User> userList = new ArrayList<User>();
    private @Getter @Setter User user;

    @PostConstruct
    public void initialize() {
        list();
    }

    public void create() {
        service.persist(user);

        stateManager.setCreate(false);
        stateManager.setList(true);
        stateManager.setSuccess(true);
    }

    public void edit() {
        service.merge(user);

        stateManager.setEdit(false);
        stateManager.setList(true);
        stateManager.setSuccess(true);
    }

    public void list() {
        userList = service.list();

        stateManager.setList(true);
    }

}

For each action method, we define which piece of our page should be rendered. For instance, consider that our form was processed, covering all of JSF lyfecycle, which implies that their values was successfully converted and validated, and our action method invoked. By using as example our create action method - see above -, we set its create flag as false because our form was converted and validated, so we do not need to show it again (Unless you want). Furthermore, we set both list and success flag as true, which indicates that the list of our page should be rendered and our form was successfully processed - You could use this flag to show something like "User created" such as bellow

<h:panelGroup rendered="#{userController.stateManager.success}">
    #{messages['default.created.message']}
</h:panelGroup>

Now, let's discuss which piece of our page should be rendered when it is called for the first time. Maybe you do not know but a void method annotated with @PostConstruct will be called first. So we define which piece of our page should be rendered. In our example, we call list method, which sets its list flag as true and populate a backing list.

@PostConstruct
public void initialize() {
    list();
}

Finally, let's review the following order nested within h:commandButton

<h:commandButton action="#{userController.create}">
    <f:ajax execute="@form" render="@all"/>
    <f:actionListener type="br.com.spa.web.faces.listener.StateManagerActionListener" />
    <f:setPropertyActionListener target="#{userController.stateManager.create}" value="true"/>
    <f:setPropertyActionListener target="#{userController.user}" value="#{user}" />
</h:commandButton>

First of all, you should call an ActionListener - here called StateManagerActionListener - which takes care of resetting any StateManager - code bellow. It must be called first before any other setPropertyActionListener designed to control any flag because the order defined within h:commandButton is the order in which they will be called. keep this in mind.

public class StateManagerActionListener implements ActionListener {

    public void processAction(ActionEvent e) throws AbortProcessingException {
        Map<String,Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
        for(Map.Entry<String,Object> entry: viewMap.entrySet()) {
            if(entry.getValue() instanceof StateManagerAwareManagedBean) {
                ((StateManagerAwareManagedBean) entry.getValue()).setStateManager(new StateManager());
            }
        }
    }

}

StateManagerAwareManagedBean - used in our ViewScoped Managed bean -, which allows that we reset any StateManager of any ManagedBean instead of resetting one by one in our ActionListener, is defined as follows

public interface StateManagerAwareManagedBean {

    StateManager getStateManager();
    void setStateManager(StateManager stateManager);

}

Second, after defining our ActionListener, we use a setPropertyActionListener which set the flag which controls the enclosing piece of the view as true. It is needed because our form is supposed to be not converted and validated. So, in our action method, we set this flag as false as discussed before.

A couple of notes

  • User is marked as a RequestScoped ManagedBean so that it can not be injected into a ViewScoped one using a ManagedProperty because its scope is shother. To overcome this issue, i set its value by using a <f:setPropertyActionListener target="#{userController.user}" value="#{user}"> - See our form
  • Our example use JEE features which need a proper Application Server. For more info, refer http://docs.oracle.com/javaee/6/tutorial/doc/
  • ManagedBean can play different roles such as a Controller, DTO and so on. When it play a role of a Controller, i prefer suffix its name with Controller. For more info, refer http://java.dzone.com/articles/making-distinctions-between
Arthur Ronald
  • 33,349
  • 20
  • 110
  • 136
  • 1
    Sad to see such an elaborated answer with no upvotes or comments, not even from OP. Keep on the hard work! – Aritz Jun 23 '17 at 11:24