Oversteer
Oversteer

Reputation: 1828

JSF dynamic loading almost working - but no commandButtons

I want to dynamically include .xhtml files at runtime, what you might call the JSF front end to an inheritance hierarchy at the entity level. I expect to have a total of 100 x 30-50 line xhtml files that would be loaded only if required to correctly handle the underlying data. A typical use case is that one or two xhtml files would be required to work on the underlying data, so you can imagine that I don't want to load the full 100 every time, it would be criminally stupid to do so.

Now I know that there are endless discussions out there on this topic but after countless hours of research I have got precisely nowhere. I am using Mojarra 2.1.1.

In the example below I initially ui:include an 'empty' file, a file with minimal content (instead of c:catch), then click a commandButton which changes the content to be included to new.xhtml and updates the container of the ui:include with ajax.

The content of the new.xhtml is shown on the page, so this looks good. The problem is that I've got a commandButton in new.xhtml, and when this file is dynamically included the commandButton will not work - the action routine is never called. If you use ui:include new.xhtml (instead of #{bean.page}) it works fine. So a few questions:

  1. Has anyone else got this working?
  2. If not is there another way of doing it?
  3. Why is something so incredibly obvious and useful so hard to do?

I had a play with jQuery load() today, and whilst I can load files using it there are various issues. The files I want to load will contain PrimeFaces dialogs and when loaded by jQuery the dialogs seemed to flash up momentarily on the screen before disappearing, forever, into the abyss. Others report components not having any css styling.

Thanks.

Edit: I should make it clear that my desire to do dynamic load rather than simply navigating to a different page is because I just want to use primefaces dialogs, a different one according to the type of underlying data. It would be a neater way of doing it, if it's possible.

index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form>
            <h:commandButton value="push" action="#{bean.doit}">
                <f:ajax render="load"/>
            </h:commandButton>
            <br/>
            <h:panelGroup id="load">
                <ui:include src="#{bean.page}"/>
                <ui:remove>
                <ui:include src="new.xhtml"/>
                </ui:remove>
            </h:panelGroup>
        </h:form>
    </h:body>
</html>

new.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core">
    <h:outputText value="new.xhtml"/>
    <br/>
    <h:commandButton value="inc" action="#{bean.inc}">
        <f:ajax/>
    </h:commandButton>
</html>

empty.xhtml

<html xmlns="http://www.w3.org/1999/xhtml"/>

Bean.java

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class Bean {

    private String page = "/empty.xhtml";

    public String doit() {
        page = "/new.xhtml";
        return null;
    }
    // getters & setters removed
}

Upvotes: 2

Views: 4619

Answers (2)

Oversteer
Oversteer

Reputation: 1828

The issue turns out to be that ui:include is implemented by a tag handler which is executed during restore view and not re-executed during invoke application, so when it modifies the component tree your application logic is not aware of the changes. This is true only when using partial state saving.

I copied the code from the ui:include tag handler into a very simple custom component and could at least get action methods to fire, but not reliably.

The solution is to exclude views requiring this dynamic ui:include functionality from using partial state saving with an entry in web.xml like this:

<context-param>
  <param-name>javax.faces.FULL_STATE_SAVING_VIEW_IDS</param-name>
  <param-value>/index.xhtml</param-value>
</context-param>

This has been tested with multiple commandButtons in multiple included page fragments and seems to work reliably (tested with ViewScoped JSF beans). I've also dynamically included a file containing a primefaces dialog and this works ok as well. I hope this is of some use to others as it's taken a good chunk of time out of my schedule, but at least it's working now, which is good.

Cheers.

Upvotes: 3

fdreger
fdreger

Reputation: 12505

You might say that the idea of JSF is that the view layer has its own MVC stack inside: each component of the view hierarchy acts as a controller (reads request data), model (stores its state) and view (renders itself). This requires that the view is stable: the view that processes requests must be the same as the view that rendered the form. Changes in the view tree that happen in the middle of the lifecycle are hard to reason about.

In your case, the problem is probably (I did not check) that the bean is request-scoped, so the page property is set to "/new.xhtml" WHILE THE BUTTON DISPLAYS, but when the action from the button gets sent back to the server, the property is "/empty.xhtml" again, so the button does not exist in the recreated view, so it does not recognize the action, so it does not execute it. Changing the scope of the bean to @ViewScoped might help.

As for "Why is something so incredibly obvious and useful so hard to do?" - it seems to me, judging from the question, that you made some major and questionable architectural design, WITHOUT following any established JSF practises and WITHOUT learning the library you made the design for (did you read the documentation? or just examples?), basing your design just on how you imagine the library should work. I would think that some shame would be more appropriate than critique and demands.

Upvotes: 1

Related Questions