Authoring JSF pages in pure Java

JSF is well known as a server side web framework to build a web application's UI with the help of components. For the overwhelming majority of users those components are represented by the tags one uses to compose a page, be it via JSP or Facelets.

The following is a typical example of a page authored with Facelets:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html">    
    <h:body>   
       #{backingBean.hello}   
        <h:form>      
            <h:inputText value="#{backingBean.name}" />                    
            <h:commandButton value="Say Hello" action="#{backingBean.sayHello}" />
        </h:form>        
    </h:body>
</html>

This may give the impression that JSF components *are* the tags shown above, and thus that JSF is necessarily about tags and XML. That is however not true.

JSF has always had a clear separation between the technology used to author pages (the so-called view description language) and the actual API to create components and compose them together into a component tree. This API is a Java API that even in the very first days of JSF had no knowledge whatsoever about JSP or tags. The strict separation allowed alternative view declaration languages like Facelets to be created and eventually be promoted to the default VDL, without needing to change the underlying JSF core framework.

Other view declaration languages have been created as well. One notable example is a concept based on JavaFX, in which a JSF page would look like this:

var x = 0;
var bindVal = "Hello";
 
function init(){
   FxPage{
      content : [
         FxForm{
                content : [
                    FxOutputLabel{
                        value : bind bindVal
                    },
                    FxCommandButton{
                        value : "Button"
                        actionListener : function() : String{
                            bindVal = "Hello {x++}";
                            return null;
                        }
                    }
                ]
            }
        ]
    }
}
See: JavaFX as JSF VDL (View Description Language)?

As it appears, the API to create components and compose a component tree is pretty accessible by itself. It could theoretically be used directly to author pages instead of solely being used as an API for VDL implementations. The problem however is that it's not clear how to actually start doing that. One trick is to use dynamic tree manipulation and add components in a PreRenderView event on an otherwise empty page.

Another way is to create a minimal VDL implementation. As a proof of concept, that's what I did. With such a thing it's possible to create a JSF application completely in Java. No Facelets or JSP files, and following Minimal 3-tier Java EE app, without any XML config, no web.xml or faces-config.xml is required either. There doesn't even have to be a WEB-INF or WebContent directory in the project.

The following shows a JSF page being defined in pure Java:

public class Intro implements Page {

    @Override
    public void buildView(FacesContext context, UIViewRoot root) throws IOException {

        ELContext elContext = context.getELContext();
        ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory();
        
        List<UIComponent> rootChildren = root.getChildren();
        
        UIOutput output = new UIOutput();
        output.setValue("<html xmlns="http://www.w3.org/1999/xhtml">");                
        rootChildren.add(output);
        
        HtmlBody body = new HtmlBody();
        rootChildren.add(body);
        
        HtmlForm form = new HtmlForm();
        body.getChildren().add(form);
        
        ValueExpression messageProperty = expressionFactory.createValueExpression(elContext, "#{myBean.message}", String.class);
        
        HtmlOutputText message = new HtmlOutputText();
        message.setValueExpression("value", messageProperty);
        form.getChildren().add(message);

        MethodExpression helloMethod = expressionFactory.createMethodExpression(elContext, "#{myBean.action}", Void.class, new Class[0]);
        
        HtmlCommandButton hiCommand = new HtmlCommandButton();
        hiCommand.setActionExpression(helloMethod);
        hiCommand.setValue("Say hello");
        form.getChildren().add(hiCommand);
        
        MethodExpression navigateMethod = expressionFactory.createMethodExpression(elContext, "#{myBean.navigate}", String.class, new Class[0]);
        
        HtmlCommandButton navigateCommand = new HtmlCommandButton();
        navigateCommand.setActionExpression(navigateMethod);
        navigateCommand.setValue("Do navigation");
        form.getChildren().add(navigateCommand);
                
        output = new UIOutput();
        output.setValue("</html>");
        rootChildren.add(output);
    }    
}

This will be rendered as follows:

The code shows how components are created simply by using the new operator and a tree is created by just inserting the child components in the children collections of their parents. Action listeners are registered on action source components by setting a method expression that points to the scoped backing bean "myBean", which is defined as follows:

@Named
@FacesConfig
public class MyBean {

    private String message;

    public void action() {
        message = "Hi!";
    }
    
    public String navigate() {
        return redirectOutcome(OtherPage.class);
    }
    
    public String getMessage() {
        return message;
    }    
}

Note that the bean features a navigation method that uses the class type of the target page to determine the navigation outcome.

By putting stuff in base classes or utility methods, page implementations can be simplified. For instance, the following shows the page where the user is navigated to after pushing the button on the first page. It inherits from a base class that already defines the html and outer body part, and only implements the body content:

public class OtherPage extends BodyTemplate {

    @Override
    public void buildBody(FacesContext context, HtmlBody body) {    
        HtmlOutputText output = new HtmlOutputText();
        output.setValue("This is a new page.");
        body.getChildren().add(output);        
    }
}

This page is rather simple and is rendered as:

As shown by the screen shots, if the project is called javavdl the initial page can be requested by requesting http://localhost/javavdl/intro.jsf, which is fully implemented using the Intro.java class given above. After clicking the navigate button, the user will be redirected to http://localhost/javavdl/test/otherPage.jsf, which is fully implemented by OtherPage.java. For this simple proof of concept, classes implementing pages have to reside in the package resources.javavdl.pages. Sub-packages in that package become paths relative to the deployment root of the application. E.g. /test/otherPage.jsf is implemented by resources.javavdl.pages.test.OtherPage.

The following is a picture of my workspace, showing that there is absolutely not a single xml file:

If this were used for real, the "src" package would probably be in a jar, and the application would consist of nothing more than this jar and the four classes shown in the demo package.

The current setup is really just a proof of concept. I personally think a declarative approach like Facelets is actually clearer, although some people actually do seem to like a purely programmatic approach to building a UI.

If people want to tinker with this, I've shared my Eclipse project where I implemented this here: github.com/arjantijms/javavdl (it currently depends on the Glassfish WTP server adapter being installed).

Arjan Tijms

Comments

  1. I made a strong app using no xhtml files. But when i needed to use Primefaces PhotoCam, is wasn't work. i don't know more what to do, to use this component to use webcams without xhtml file.

    ReplyDelete

Post a Comment

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

Jakarta EE Survey 2022

What’s new in Jakarta Security 3?