Blog-Archiv

Donnerstag, 19. September 2019

Java Swing App Too Small on High Resolution Screen

The artifacts of software production are aging like living creatures. For some years I wondered how long my Java Swing desktop applications are going to survive. After JavaFX was promoted and web development became dominant, Swing apps are endangered species.

Swing was the first complete operating-system-independent object-oriented graphical user interface on planet Earth. (AWT renders platform-dependent, and although it could be used on all platforms, important components like trees and tables were missing, and still are.) Swing comes packaged with the Java Runtime Environment (JRE). Lots of people spent a lot of time creating Swing apps, and lots of users consumed them. Web apps still haven't reached the ease of use of desktop apps, and it looks like they don't strive for that.

The Problem

So time is washing away everything, and I saw my 20 years old Swing file explorer very small on the horizon of my high-DPI screen on a WINDOWS-10 machine (didn't encounter this problem on LINUX). Following screenshot is original size:

The title bar (not part of Swing) was the only natural thing, everything else was definitely much too small. Note how big the mouse cursor on bottom is compared to the toolbar icons.

There was no application-modification that could have caused this problem. Must be the hardware, or the operating system, or .... Swing being too naive for modern high-resolution screens?

The Solution

As we all believe in God, Google and Stackoverflow, I tried to find a fix for this. Here is the link that saved my Swing app for now:

Mind that the command-line option -Dsun.java2d.dpiaware=false does not work for Java 1.8. When you scroll downwards on that page, you find the real instruction:

  • → With WINDOWS Explorer, find your installed java.exe and javaw.exe interpreters (cmd.exe: where java ↵)

  • → For each, right-click "Properties"

  • → Change to "Compatibility" tab

  • → Click on "High DPI settings"

  • → Activate checkbox "Override high DPI scaling behavior"

  • → Choose "System" from "Scaling performed by" choice below

That means, you let the operating system take care of scaling the app for high DPI screens, not Swing. Generally Swing should be able to do that, but ....

After restarting my app it looked like this, in original size:

That really saved my day!




Samstag, 14. September 2019

JSF AJAX Test Page

To get used to the JSF AJAX facilities you should play around with it for some time. This Blog provides a basic implementation with different AJAX and non-AJAX callbacks to start with.

The execute attribute of an <f:ajax> element enumerates the ids of fields to take to the server when called, the render attribute enumerates ids of elements to update when the asynchronous response comes back from the server. Optionally the event attribute enumerates events (e.g. "click") that should trigger the AJAX call.

Remarkable:

  • From the execute and render attributes of an <f:ajax> element, you can not easily refer to element-ids being in another form (in case several forms are on a page)
  • Fields that are not in a form are not taken to the server, even when referenced explicitly
  • The viewId, as retrieved from FacesContext.getCurrentInstance().getViewRoot().getViewId(), is the same on every browser tab, and in any browser. Also the clientId is the same everywhere, additionally it is identical to the id. None of them identifies the JSF user interface instance (browser tab/window).

Contents

  1. Application Screenshots
  2. Full Source Code

Application Screenshots

In the following you see screenshots of the user interface, and explanations about what has been clicked and typed on the UI, and how this was implemented in the application.

Mind that I changed to a new browser-tab several times during these screenshots, so the shown identities and times are not consistent.

User Interactions

  1. Initial State
  2. Typing "Name"
  3. Selecting "Language"
  4. Clicking "AJAX commandButton"
  5. Clicking "Listener commandButton"
  6. Clicking "Action commandButton"
  7. "Action commandButton" without POST-Redirect-GET

The test page contains two forms and some command-buttons. It displays its own log messages on bottom. Following is the look when loading it into a browser:

The blue area on top is the observed and logged input form. Below are some informations about the HTTP session and the JSF view. On bottom are the log messages, generated by the Java controller when some callback is invoked. It logs the time, phase and message-text of any server-side event.

Initially just the controller constructor logged its creation in RENDER_RESPONSE phase. In field "HTTP Method" we see that a GET was used to read the JSF page.

    public PersonController() {
        log("PersonController() constructor");
        creationTime = logging().getCurrentTime();
    }

After entering some text in "Name" field and clicking elsewhere into the page (to make the browser trigger the change-event) it looks like this:

We see that, in PROCESS_VALIDATIONS phase, a valueChange callback was called, and consequently in UPDATE_MODEL_VALUES the new value was set into the bean. Then, in INVOKE_APPLICATION phase, an AJAX callback was triggered.

These three things were caused by a valueChangeListener attribute on the text field, the data-binding in value attribute, and an AJAX listener in a nested <f:ajax> element. The render-id "fieldsToUpdateByAjax" is the information- and logging-area below the blue panel, which should be updated with server-information on any event.

                    <h:inputText value="#{personController.name}" id="textInputId"
                            valueChangeListener="#{personController.textValueChangeCallback}">
                        
                        <f:ajax render="fieldsToUpdateByAjax"
                                listener="#{personController.textAjaxCallback}"/>
                    </h:inputText>

Mind that we see the old field value (null) just in the valueChange callback, but no more in the AJAX callback. There is a workaround to queue a valueChange event to the INVOKE_APPLICATION phase, so that the business logic can access also the old value, by calling event.setPhaseId(PhaseId.INVOKE_APPLICATION); event.queue().

In field "HTTP Method" we see that a POST was used to perform the AJAX call.

When we now clear the messages, and then select "de" as "Language":

The same happened for the language selection-menu: a valueChange callback, then the bean setter, then an AJAX callback.

                    <h:selectOneMenu value="#{personController.language}"  id="selectInputId"
                            valueChangeListener="#{personController.selectValueChangeCallback}">
                            
                        <f:selectItems value="#{personController.languages}"/>
                        
                        <f:ajax render="fieldsToUpdateByAjax"
                                listener="#{personController.selectAjaxCallback}"/>
                    </h:selectOneMenu>

Clear messages, then press "AJAX commandButton". Looks like this:

The "AJAX commandButton" has been programmed to take the "Name" text field to the server (see execute=...), but not the "Language" selection field, thus we see the name again being put into the bean, but not the language. Then an AJAX callback is invoked.

                    <h:commandButton value="AJAX commandButton"> <!-- Doesn't take selectInputId to server! -->
                        <f:ajax execute="textInputId"
                                render="fieldsToUpdateByAjax" 
                                listener="#{personController.commandAjaxCallback}"/>
                    </h:commandButton>

Two more buttons to study. The "Listener commandButton" actually is an actionListener. When clearing messages and pressing that button, following happens:

We see that this button submitted the full form ("Name" and "Language" fields), but it did not reset the form for new input. The listener method was finally executed in INVOKE_APPLICATION phase.

                    <h:commandButton value="Listener commandButton"
                            actionListener="#{personController.commandListenerCallback}"/>

Now lets clear the messages and press the "Action commandButton", which is the normal JSF form submitter.

The "Action commandButton" button did the same as the "Listener commandButton", it took all fields in the form to the server, but after that it reloaded the page to get a new empty input form. We can see that the PersonController constructor has been called again.

                    <h:commandButton value="Action commandButton"
                            action="#{personController.commandActionCallback}"/>

This is the Java controller part to do the reload:

    public String commandActionCallback()   {
        log("commandActionCallback(), postRedirectGet="+postRedirectGet);
        if (postRedirectGet == false)
            return null;    // stay on same page
        
        String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
        return viewId+"?faces-redirect=true";    // reload page
    }

We see that there is a switch that lets us choose whether the button reloads the page or just saves the properties to the bean. Now lets deactivate this by clicking onto the "POST-Redirect-GET" checkbox, and then press "Action commandButton" again.

We see that the fields were not cleared, and the PersonController constructor has not been called. Anyway the input data have been saved to the bean. So this seems to be the same as "Listener commandButton".


Full Source Code

There are no special configurations, no JPA involved, stick to web.xml, faces-config.xml, beans.xml, ApplicationFacesConfig.java as I showed them in my recent Blog about setup of a JSF project in Eclipse. I called the project jsfAjax, put this as artifactId into the Maven pom.xml.

Project Structure

Files

  1. index.xhtml
  2. Person.java
  3. PersonController.java
  4. Logging.java

index.xhtml

Remarkable: in JSF, you can avoid the browser-specific default action when the user presses the ENTER key by adding following attribute to any input field (except textarea): onkeypress="if (event.keyCode === 13) return false". See <h:inputText> with id="textInputId". When you remove this and press ENTER while standing in the "Name" text field, the first found submit-button would get triggered by the browser; in this case it would be the "AJAX commandButton", which may not be what we want.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
>
    <h:head><title>JSF AJAX Example</title></h:head>
    
    <h:body>
        <h2>JSF AJAX Test Form</h2>
        
        <h:form style="background-color: lightBlue;">
            <h:panelGrid columns="2">
                <h:panelGrid columns="2">
                    <h:outputLabel>Name</h:outputLabel>
                    <h:inputText value="#{personController.name}" id="textInputId"
                            valueChangeListener="#{personController.textValueChangeCallback}"
                            onkeypress="if (event.keyCode === 13) return false;">
                            <!-- Avoid browser default which would trigger first submit-button on page -->
                        
                        <f:ajax render="fieldsToUpdateByAjax"
                                listener="#{personController.textAjaxCallback}"/>
                    </h:inputText>
                    
                    <h:outputLabel>Language</h:outputLabel>
                    <h:selectOneMenu value="#{personController.language}"  id="selectInputId"
                            valueChangeListener="#{personController.selectValueChangeCallback}">
                            
                        <f:selectItems value="#{personController.languages}"/>
                        
                        <f:ajax render="fieldsToUpdateByAjax"
                                listener="#{personController.selectAjaxCallback}"/>
                    </h:selectOneMenu>
                </h:panelGrid>
                
                <h:panelGrid columns="1">
                    <h:commandButton value="AJAX commandButton"> <!-- Doesn't take selectInputId to server! -->
                        <f:ajax execute="textInputId"
                                render="fieldsToUpdateByAjax" 
                                listener="#{personController.commandAjaxCallback}"/>
                    </h:commandButton>
                    
                    <h:commandButton value="Listener commandButton"
                            actionListener="#{personController.commandListenerCallback}"/>
                    
                    <h:commandButton value="Action commandButton"
                            action="#{personController.commandActionCallback}"/>
                            
                </h:panelGrid>
            </h:panelGrid>
        </h:form>
        
        <hr/>
        
        <h:form id="fieldsToUpdateByAjax">
        <!-- AJAX-fields in other form can not see fields inside here, but they can see this form -->
            <h:selectBooleanCheckbox value="#{personController.postRedirectGet}" id="postRedirectGetId">
                <f:ajax render="fieldsToUpdateByAjax"/>
            </h:selectBooleanCheckbox>
            <h:outputLabel for="postRedirectGetId">Make "Action commandButton" perform POST-Redirect-GET</h:outputLabel>
                 
            <h:panelGrid columns="2">
                <h:outputLabel value="viewId:"/>#{view.viewId}
                <h:outputLabel value="id, clientId:" />#{view.id}, #{view.clientId}
                <h:outputLabel value="Session Start:"/> #{logging.creationTime}
                <h:outputLabel value="View Creation:"/> #{personController.creationTime}
                <h:outputLabel value="Latest Request:"/> #{personController.requestTime}
                <h:outputLabel value="HTTP Method:"/>#{request.method}
                <h:outputLabel value="Bean-Identity:"/>#{personController.beanIdentity}
                <h:outputLabel value="ViewRoot-Identity:"/>#{personController.viewRootIdentity}
            </h:panelGrid>
            
            <hr/>

            <h:outputLabel><b>Log</b> (session-scoped)</h:outputLabel>
            
            <h:commandButton value="Clear">
                <f:ajax render="fieldsToUpdateByAjax" listener="#{personController.clearLog}"/>
            </h:commandButton>
    
            <h:selectBooleanCheckbox value="#{logging.newestOnTop}" id="newestOnTopId">
                <f:ajax render="fieldsToUpdateByAjax"/>
            </h:selectBooleanCheckbox>
            <h:outputLabel for="newestOnTopId">Newest on Top</h:outputLabel>
                 
            <h:dataTable value="#{logging.messages}" var="message" style="width: 100%;" border="1" cellpadding="3">
                <h:column><f:facet name="header">Time</f:facet>#{message.time}</h:column>
                <h:column><f:facet name="header">Phase</f:facet>#{message.phase}</h:column>
                <h:column><f:facet name="header">Message</f:facet>#{message.text}</h:column>
            </h:dataTable>
        
        </h:form>
        
    </h:body>
</html>

Person.java

This normally would be an @Entity stored in some database.

package fri.jsf;

import java.io.Serializable;

public class Person implements Serializable
{
    private String name;
    private String language;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    public String getLanguage() {
        return language;
    }
    public void setLanguage(String language) {
        this.language = language;
    }
}

PersonController.java

This "backing bean" serves both the blue input-form and the information- and logging-area below. Any logging message is generated here.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package fri.jsf;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.ValueChangeEvent;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class PersonController implements Serializable
{
    private Person person = new Person();
    private List<String> languages = Arrays.asList(new String[] { "en", "fr", "de" });
    
    private boolean postRedirectGet = true;
    private final String creationTime;
    private String requestTime;

    public PersonController() {
        log("PersonController() constructor");
        creationTime = logging().getCurrentTime();
    }
    
    // form properties
    
    public String getName() {
        return person.getName();
    }
    public void setName(String name) {
        log("setName("+logging().formatString(name)+")");
        person.setName(name);
    }
    
    public String getLanguage() {
        return person.getLanguage();
    }
    public void setLanguage(String language) {
        log("setLanguage("+logging().formatString(language)+")");
        person.setLanguage(language);
    }
    
    public List<String> getLanguages() {
        return languages;
    }
    
    // text field callbacks
    
    public void textValueChangeCallback(ValueChangeEvent changeEvent)   {
        log("textValueChangeCallback("+logging().formatChangeEvent(changeEvent)+")");
    }

    public void textAjaxCallback(AjaxBehaviorEvent behaviorEvent)   {
        log("textAjaxCallback()");
    }
    
    // select field callbacks
    
    public void selectValueChangeCallback(ValueChangeEvent changeEvent)   {
        log("selectValueChangeCallback("+logging().formatChangeEvent(changeEvent)+")");
    }

    public void selectAjaxCallback(AjaxBehaviorEvent behaviorEvent)   {
        log("selectAjaxCallback()");
    }
    
    // button callbacks
    
    public String commandActionCallback()   {
        log("commandActionCallback(), postRedirectGet="+postRedirectGet);
        if (postRedirectGet == false)
            return null;    // stay on same page
        
        String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
        return viewId+"?faces-redirect=true";    // reload page
    }

    public void commandListenerCallback(ActionEvent actionEvent)   {
        log("commandListenerCallback()");
    }
    
    public void commandAjaxCallback(AjaxBehaviorEvent behaviorEvent)   {
        log("commandAjaxCallback()");
    }
    
    // logging area
    
    public boolean isPostRedirectGet() {
        return postRedirectGet;
    }
    public void setPostRedirectGet(boolean postRedirectGet) {
        System.out.println("setPostRedirectGet("+postRedirectGet+")");
        this.postRedirectGet = postRedirectGet;
    }
    
    public String getCreationTime() {
        return creationTime;
    }
    
    public String getRequestTime() {
        return requestTime;
    }
    
    public String getBeanIdentity() {
        return String.valueOf(System.identityHashCode(this));
    }
    
    public String getViewRootIdentity() {
        return String.valueOf(System.identityHashCode(FacesContext.getCurrentInstance().getViewRoot()));
    }
    
    public void clearLog(AjaxBehaviorEvent behaviorEvent)   {
        logging().clear(null);
        System.out.println("clearLog()");
    }
    
    private void log(String message)  {
        requestTime = logging().getCurrentTime();
        logging().addMessage(message);
    }
    private Logging logging()  {
        return javax.enterprise.inject.spi.CDI.current().select(Logging.class).get();
    }
}

Logging.java

This holds the list of logging messages. The PersonController was bound to view-scope, but to be able to observe everything at any time, this is bound to session-scope. It provides presentation logic for the area below the blue input-form.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package fri.jsf;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.ValueChangeEvent;
import javax.inject.Named;

@Named
@SessionScoped
public class Logging implements Serializable
{
    public static class Message implements Serializable
    {
        // it is not possible with JSF to have this public final without getters!
        private final String time;
        private final String phase;
        private final String text;
        
        public Message(String time, String phase, String text) {
            this.time = time;
            this.phase = phase;
            this.text = text;
        }
        public String getTime() {
            return time;
        }
        public String getPhase() {
            return phase;
        }
        public String getText() {
            return text;
        }
        @Override
        public String toString() {
            return time+" | "+phase+" | "+text;
        }
    }

    
    private String creationTime;
    private List<Message> messages = new ArrayList<>();
    private boolean newestOnTop = false;

    public String getCreationTime() {
        if (creationTime == null)
            creationTime = getCurrentTime();
        return creationTime;
    }
    
    public List<Message> getMessages() {
        return messages;
    }
    
    public void clear(AjaxBehaviorEvent behaviorEvent) {
        messages.clear();
    }
    
    public boolean isNewestOnTop() {
        return newestOnTop;
    }
    public void setNewestOnTop(boolean newestOnTop) {
        this.newestOnTop = newestOnTop;
        Collections.reverse(messages);
    }
    
    public void addMessage(String message)  {
        String time = getCurrentTime();
        String phase = ""+FacesContext.getCurrentInstance().getCurrentPhaseId();
        
        Message m = new Message(time, phase, message);
        if (newestOnTop)
            messages.add(0, m);
        else
            messages.add(m);
        
        System.out.println(m);
    }
    
    public String formatChangeEvent(ValueChangeEvent event) {
        return formatString(event.getOldValue())+" -> "+formatString(event.getNewValue());
    }
    
    public String formatString(Object s) {
        return (s == null) ? "null" : "\""+s+"\"";
    }
    
    public String getCurrentTime() {
        return new SimpleDateFormat("HH:mm:ss:SSS").format(new Date());
    }
}


Conclusion

JSF Blogs are as long as the learning curve is steep. Hope this article helps :-!




Sonntag, 8. September 2019

JSF View Reuse Mechanisms

JSF features several code reuse mechanisms for XHTML views. Here are those without need of additional Java code, ordered by complexity, simplest first.

  1. Including
    Done via element ui:include with src="includes/file.xhtml" attribute, and optional nested ui:param that supports #{} bean-properties, but not methods. CAUTION: When including relatively from a sub-directory, and the included file also includes relatively, that second include will fail!

  2. Templating
    The includer can override elements, marked by ui:insert, through ui:define, and can pass ui:param parameters, but also no support for methods. It is possible to inject complete HTML fragments into the included template. Useful for unified look and layout. Namespace prefix "ui:", insert, define, composition, decorate, ....

  3. Composite Component
    Advanced JSF 2 component concept. Combines a number of existing components with markup to form new components. Namespace prefix "cc:" or "composite:", interface, attribute, implementation, .... Result is an XML-element with an arbitrary name and namespace below http://xmlns.jcp.org/jsf/composite/. Makes a XHTML view reusable for different Java controllers, because the parameter attributes can be evaluated with all kinds of #{} expressions, even methods.

The differences are not that big. Inclusion does not provide method parameter passing, but it can be parameterized with Java properties. Templating doesn't provide methods either, but enables inserting whole XHTML fragments into the included file. Composite component finally allows method parameter passing, and its final appearance is a completely new XML element type, driven by attributes.

Setup

Please refer to my recent Blog for a JSF 2.3 setup with Eclipse, there you find the Maven pom.xml, Servlet web.xml, JSF faces-config.xml, CDI beans.xml and ApplicationFacesConfig.java.

I found out that you can omit the Eclipse configuration when you do following:

  • In a file explorer, create a simple Maven project directory structure (not a "dynamic web project" archetype!)
  • Add pom.xml with JSF dependencies in its root directory
  • Run Maven build from commandline: mvn package
  • Import the project into Eclipse as "Maven project" (not as "Java project"!)

Example Index

  1. Including
  2. Templating
  3. Composite Component

1. Including

This example renders dates in a special way. Here is a screenshot of the user interface:

Project source files and directories:

includer.xhtml (master view)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
>
    <h:head>
        <title>JSF Include Example</title>
    </h:head>
    
    <h:body>
        <h2>JSF &lt;ui:include&gt; Example with &lt;ui:param&gt;</h2>
        
        <span>
            Once the 
            <ui:include src="includes/date-box.xhtml">
                <ui:param name="label" value="Creation Date"/>
                <ui:param name="value" value="#{dateHolder.creationDate}"/>
                <ui:param name="pattern" value="d.M.yyyy, HH:mm"/>
            </ui:include>
        </span>
        
        <span>
            mattered, but now it's just the
            <ui:include src="includes/date-box.xhtml">
                <ui:param name="label" value="Latest Update"/>
                <ui:param name="value" value="#{dateHolder.lastUpdate}"/>
                <ui:param name="borderColor" value="red"/>
            </ui:include>
            ....
        </span>
    </h:body>
    
</html>

date-box.xhtml (the included XHTML)

<!DOCTYPE html>
<!-- 
    Formatted date with titled border.
 -->
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
    <h:body>
        <ui:param name="borderColor" value="#{empty borderColor ? 'black' : borderColor}"/>
        <ui:param name="pattern" value="#{empty pattern ? 'yyyy-MM-dd HH:mm' : pattern}"/>
        
        <fieldset style="
                display: inline-block;
                border-radius: 0.6em;
                border-color: #{borderColor};
        ">
            <legend style="color: #{borderColor};">#{label}</legend>
        
            <h:outputText value="#{value}">
                <f:convertDateTime pattern="#{pattern}"/>
            </h:outputText>
        </fieldset>
    </h:body>

</html>

DateHolder.java (Java bean that provides dates, used by the master view)

package fri.jsf;

import java.io.Serializable;
import java.time.Instant;
import java.util.Date;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class DateHolder implements Serializable
{
    public Date getCreationDate() {
        return Date.from(Instant.EPOCH);
    }
    
    public Date getLastUpdate() {
        return new Date();  // now
    }
}

You don't need setters here, these date properties are read-only.

Explanations

Inclusion can happen everywhere, in this case among inline elements. The parametrization is easy to understand, although restricted to Java bean properties and plain text. As you see we can pass a date into date-box.xhml, and it gets formatted there, so parametrization is not restricted to strings.

Have a eye on the parameters in includer.xhml and their re-definitions inside date-box.xhml. That's the way to establish default values. You can use the same parameter-names on both sides!

We make the first date-box use another date-format pattern than the second.
We insert the borderColor parameter into CSS code to set a red color.


2. Templating

This example lays out a scrolling web page with a fixed header, the header having one or two lines. The first header line is divided into left, center and right part. Here is a screenshot with a one-line header:

File structure:

templater.xhtml (master view)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
>
    <h:head>
        <title>JSF Template Example</title>
    </h:head>
    
    <h:body>
        <!-- Everything outside <ui:composition> will be ignored! -->
        
        <ui:composition template="templates/sticky-header-page.xhtml">
            <ui:param name="headerBackColor" value="orange"/>

            <ui:define name="headerTextLeft"> <!-- Explicitly erase left side default text -->
            </ui:define>
            
            <ui:define name="headerTextCenter">
                Pirate Adventures
            </ui:define>
            
            <!-- ui:define name="headerSecondLine">
                Second Line
            </ui:define -->
            
            <ui:define name="contentText">
                <ui:include src="includes/long-text.html"/>
            </ui:define>
            
        </ui:composition>
        
        <!-- To not ignore everything outside <ui:composition>, use <ui:decorate> -->
    </h:body>
    
</html>

sticky-header-page.xhtml (the template)

<!DOCTYPE html>
<!-- 
    A fixed header and a page scrolling underneath.
    The header has a left/center/right part, and an optional second line.
 -->
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:h="http://xmlns.jcp.org/jsf/html">
    
    <h:body>
        <!-- The fixed page header -->
        <div style="
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                text-align: center;
                padding: 0.5em;
                background-color: #{empty headerBackColor ? 'lightGray' : headerBackColor};
        ">
            <div>
                <span style="float: left;">
                    <ui:insert name="headerTextLeft">
                        [Author]
                    </ui:insert>
                </span>
                <span><b>
                    <ui:insert name="headerTextCenter">
                        [Title]
                    </ui:insert>
                </b></span>
                <span style="float: right;">
                    <ui:insert name="headerTextRight">
                        [Date]
                    </ui:insert>
                </span>
            </div>
            
            <div>
                <ui:insert name="headerSecondLine">
                    <ui:param name="noSecondLine" value="true" />
                </ui:insert>
            </div>
        </div>
        <div style="clear: both;"></div>
        
        <!-- The scrolling page content -->
        <div style="padding-top: #{noSecondLine ? '2em' : '3em'};">
            <ui:insert name="contentText"/>
        </div>
        
    </h:body>

</html>

long-text.html (long example text for scrollbars, goes to contentText)

<!DOCTYPE html>
<html>
<body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
......
</body>
</html>

Copy the complete nonsense text from lipsum.com to make the page scroll.

Explanations

Parameterization is the same as with inclusions. Here we have headerBackColor as plain <ui:param>, which sets an orange background color to the header. The difference between parameters and insertions is that the latter can receive entire HTML fragments.

Essential is that any name attribute in a <define> of the master page matches a name attribute in an <insert> of the template. Such insertion points are headerTextLeft, headerTextCenter, headerTextRight, headerSecondLine, and contentText.

Mind that not all insertion points are used, and that the insertion points sometimes define defaults and sometimes not. In case they define defaults, you can erase those explicitly by passing empty, as done with headerTextLeft (see screnshot, left text is not present). In case no define was done, the insert's default will be visible when present, like in headerTextRight. On the other hand the headerSecondLine insertion point does not have a default and will not be present when no define was done for it.

This opens a dependency - the template's layout depends on the presence of headerSecondLine, because the padding-top of the contentText must be set correctly to not obscure the scrolling text underneath. The nested <ui:param> noSecondLine will be true when no define for headerSecondLine was done. In that case the padding-top goes to 3em, else it will be 2em. Setting such a <ui:param> is a trick to find out whether a define was done or not, as the whole headerSecondLine insertion point would be removed when being replaced by a define. Try commenting in the out-commented headerSecondLine in templater.xhtml master file, and check that the padding-top automatically grows!

The contentText gets replaced by a HTML file loaded through <ui:include>. Such can be mixed in at any time.

Please mind that, when you have a file containing <ui:composition>, JSF would ignore anything outside that tag. If you don't like that, use <ui:decorate> instead.


3. Composite Component

Example is a text field with a label and a submit-button, reused to enter either a name or an e-mail (both have their own button). Whatever was entered by the user gets stored in the backing bean, and is rendered in the list at bottom. The screenshot shows a state when two names and one e-mail were entered:

Source files:

composer.xhtml (master view)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:cc="http://xmlns.jcp.org/jsf/composite"
      xmlns:tl="http://xmlns.jcp.org/jsf/composite/textline"
>
    <h:head>
        <title>JSF Composite Example</title>
    </h:head>
    
    <h:body>
        <h2>JSF &lt;composite&gt; Example</h2>
        
        <tl:textlineSubmit
            label="Name"
            text="#{personTextlinesController.name}"
            buttonLabel="Register"
            buttonAction="#{personTextlinesController.submitName}"
        />
        
        <tl:textlineSubmit
            label="E-Mail"
            text="#{personTextlinesController.email}"
            buttonLabel="Send"
            buttonAction="#{personTextlinesController.submitEmail}"
        />
        
        <h:dataTable value="#{personTextlinesController.summary}" var="item">
            <h:column>#{item}</h:column>
        </h:dataTable>
    </h:body>
    
</html>

textlineSubmit.xhtml (the component)

<!DOCTYPE html>
<!-- 
    A label with a input text field and a command-button.
 -->
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:composite="http://xmlns.jcp.org/jsf/composite"
        xmlns:h="http://xmlns.jcp.org/jsf/html">
    
   <composite:interface>
      <composite:attribute name="label" default="Input"/>
      <composite:attribute name="text"/>
      <composite:attribute name="buttonLabel" default="Submit"/>
      <composite:attribute name="buttonAction" method-signature="java.lang.String method()" required="true"/>
   </composite:interface>
   
   <composite:implementation>
      <h:form>
         <h:panelGrid columns="3">
             #{cc.attrs.label}: 
             
             <h:inputText value="#{cc.attrs.text}"/>
             
             <h:commandButton value="#{cc.attrs.buttonLabel}"
                 action="#{cc.attrs.buttonAction}"/>
         </h:panelGrid>
      </h:form>
   </composite:implementation>

</html>

PersonTextlinesController.java (Java controller, used by the master view)

package fri.jsf;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class PersonTextlinesController implements Serializable
{
    private String name;
    private String email;
    
    private List<String> summary = new ArrayList<>();
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String submitName()  {
        summary.add("name="+name);
        return "";
    }
    
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String submitEmail()  {
        summary.add("email="+email);
        return "";
    }
    
    public List<String> getSummary() {
        return summary;
    }
}

Explanations

The composer.xhtml master page uses two instances of the component, parameterized through attributes of the component tag, which are label, text, buttonLabel and buttonAction. The labels are plain texts, text is a Javabean property, buttonAction is a Java bean method. This makes composite component special. So, here parameterization happens through attributes of the XML-element that represents the component.

We can set the buttons of the two component instances to different callbacks of the backing Java bean. The Java bean returning "" from these callbacks makes JSF stay on the same page. The list on bottom mirrors the history of inputs. Mind that this page suffers from the double-submit problem, refreshing the browser page submits a value.

The texlineSubmit.xhtml component is built like a function. It has a signature-like interface section, and body-like implementation section. Mind that the interface attributes allow definition of default values. Inside the implementation, you refer via cc.attrs.xxx to interface-attribute xxx, this prefix is hardcoded in JSF.

The component's element-tag <texlineSubmit> is the file basename of the component (texlineSubmit.xhtml), and that file must reside in

src/main/webapp/resources/textline/

when the namespace should be http://xmlns.jcp.org/jsf/composite/textline, like it is referenced in head of composer.xhtml.

The PersonTextlinesController bean is @ViewScoped to not lose the list of inputs. Every bean-property also needs an action-callback for that property. In real life we would use different controllers for each instance of a composite component.


Conclusion

It's a little bit hard to know what to use when. The composite for sure is the most reusable component, but it is also the most complex. Still there are more options, e.g. Java tag handlers. JSF is big.