One of the features in Java Server Faces that distinguishes it from for example JSP technology, is the ability to access UI Components programmatically, at run-time. This makes it possible not only to inspect the state of these components, but also to manipulate them. One advanced type of manipulation is the run-time creation of new components: reading some kind of meta-data that describes the components that are desired, our application can extend pages while the application is running (and only for the current session context). Our pages now consist of part static definitions, created using JSF JSP tags in JSP pages and part dynamically added or modified components.
In this article I will demonstrate how such manipulation can be implemented. The case here is a prototype for an Evaluation Application for our technische sessions. The session coordinator can predefine the questions he wants to put to the attendants. Based on these question-definitions, each session attendant will be presented with a web-page with all the questions and (multiple choice) answer options. This same approach can be used for Quiz and on Line Exam applications. The JSF implementation used is the standard JSR (Sun) Reference Implementation (1.1).
With a simple JSF JSP page, I create a UI where a Poll Question can be defined. The question text is entered in an InputText. Then we can specify whether the question requires an Open Answer – free text as typed by the user -, a single selection from a predefined set of multiple options or possibly multiple selections from that set of options. Next we can pick the widget to use for this question. Note: this is only relevant if the Answer Style is not Open Answer! We can choose between a tick-in-the-box widget (checkbox or radio button, depending on whether it is a single or multiple answer), a list or a menu style item.
The item Answer Options is where we define the various options that the user may pick his answers from. This is done in the format:
value,label
value,label
If we click the Rephrase Question button, we see the display of the question and allowable answers that we specified.
The JSF code that defines this page is fairly simple. The most important part probably is the part we cannot see in the screenprint above:
...
<body style="font-family:sans-serif" ><h:form id="form1">
<h:outputText value="Poll Picker - Compose a Poll Question"
id="outputText1"
style="font-family:sans-serif; font-size:x-large; font-weight:bolder; text-decoration:underline;"/>
<h:panelGrid columns="2" binding="#{pollPickerBacker.panelGrid1}"
id="panelGrid1">
<h:outputLabel value="Question"
id="outputLabel2" for="question"/>
<h:inputText id="question"
title="Type the question text that will be presented to the user"
size="80" value="#{pollPickerBacker.question}"/>
<h:outputLabel value="Answer Style"
id="outputLabel1"
title="Indicate the number of answers that can be given to the question"
for="answerStyle"/>
<h:selectOneRadio value="#{pollPickerBacker.answerStyle}"
id="answerStyle">
<f:selectItem itemLabel="Open Answer" itemValue="o"
id="selectItem0"/>
<f:selectItem itemLabel="Single Answer" itemValue="s"
id="selectItem1"/>
<f:selectItem itemLabel="Multiple Answers" itemValue="m"
id="selectItem2"/>
</h:selectOneRadio>
<h:outputLabel value="Widget style"
id="outputLabel3"
title="Note: not relevant when Open Answer is selected"/>
<h:selectOneRadio value="#{pollPickerBacker.widgetStyle}"
id="widgetStyle">
<f:selectItem itemLabel="Radio/Check" itemValue="tick"
id="selectItem3"/>
<f:selectItem itemLabel="List" itemValue="list"
id="selectItem4"/>
<f:selectItem itemLabel="Menu" itemValue="menu"
id="selectItem5"/>
</h:selectOneRadio>
<h:outputLabel value="Answer options"
id="outputLabel4"/>
<h:inputTextarea id="inputTextarea1" cols="40" rows="5"
value="#{pollPickerBacker.answerOptions}"/>
<h:commandButton value="Rephrase Question"
id="commandButton1" type="submit"
action="#{pollPickerBacker.rephraseQuestionAction}"/>
</h:panelGrid>
<hr/>
<h:outputText value="Could you please answer the following question:"
id="outputText2"
style="font-family:sans-serif; font-size:x-large; font-weight:bolder; text-decoration:underline;"/>
<p>
<h:messages/><h:panelGrid binding="#{pollPickerBacker.questionPanel}"
id="questionPanel" columns="2"/>
</p>
</h:form></body>
</html>
</f:view>
</jsp:root>
That most important definition is the PanelGrid underneath the heading "Could you please answer the following question:". This component has a binding defined: binding="#{pollPickerBacker.questionPanel}". This makes the PanelGrid accessible in the pollPickerBacker bean which in turns allows us to add newly created UI Components to it. Note: we could also have used FacesContext.getCurrentInstance().getViewRoot().findComponent() to get hold of this component instead of using the binding property.
The Backing Bean
The Backing Bean not only holds the values for Question, AnswerStyle, WidgetStyle and AnswerOptions, it also binds the panelGrid and implements the ActionListener method activated by the CommandButton rephrase question. The method rephraseQuestionAction on this bean is where all the action is:
public String rephraseQuestionAction() {
Application application =
FacesContext.getCurrentInstance().getApplication();
List children = getQuestionPanel().getChildren();
children.clear();
// create ques tion text
HtmlOutput Label questionPrompt =
(HtmlOutputLabel)application.createComponent(HtmlOutputLabel.COMPONENT_TYPE);
questionPrompt.setValue("Question:");
HtmlOutputText question =
(HtmlOutputText)application.createComponent(HtmlOutputText.COMPONENT_TYPE);
question.setValue(getQuestion());
HtmlOutputLabel answerPrompt =
(HtmlOutputLabel)application.createComponent(HtmlOutputLabel.COMPONENT_TYPE);
answerPrompt.setValue("Answer:");
children.add(questionPrompt);
children.add(question);
children.add(answerPrompt);
// create answer elements (either InputText or a SelectOne or SelectMany)
UIComponent answer = null;
ValueBinding answerBinding = null;
if ("o".equalsIgnoreCase(getAnswerStyle())) {
answer = (HtmlInputText)application.createComponent(HtmlInputText.COMPONENT_TYPE);
LengthValidator lengthValidator =
(LengthValidator)application.createValidator(LengthValidator.VALIDATOR_ID);
lengthValidator.setMinimum(2); // an answer must have at least two characters
lengthValidator.setMaximum(200); // an answer must be no longer than 200 characters
((HtmlInputText)answer).addValidator(lengthValidator);
// bind the value property to the answer property in the pollPickerBacker bean
answerBinding = application.createValueBinding("#{pollPickerBacker.answer}");
} else {
if ("s".equalsIgnoreCase(getAnswerStyle())) {
if ("tick".equalsIgnoreCase(getWidgetStyle())) {
answer = (HtmlSelectOneRadio)application.createComponent(HtmlSelectOneRadio.COMPONENT_TYPE);
}
if ("list".equalsIgnoreCase(getWidgetStyle())) {
answer = (HtmlSelectOneListbox)application.createComponent(HtmlSelectOneListbox.COMPONENT_TYPE);
}
if ("menu".equalsIgnoreCase(getWidgetStyle())) {
answer = (HtmlSelectOneMenu)application.createComponent(HtmlSelectOneMenu.COMPONENT_TYPE);
}
answerBinding = application.createValueBinding("#{pollPickerBacker.answer}");
} else if ("m".equalsIgnoreCase(getAnswerStyle())) {
if ("tick".equalsIgnoreCase(getWidgetStyle())) {
answer = (HtmlSelectManyCheckbox)application.createComponent(HtmlSelectManyCheckbox.COMPONENT_TYPE);
}
if ("list".equalsIgnoreCase(getWidgetStyle())) {
answer = (HtmlSelectManyListbox)application.createComponent(HtmlSelectManyListbox.COMPONENT_TYPE);
}
if ("menu".equalsIgnoreCase(getWidgetStyle())) {
answer = (HtmlSelectManyMenu)application.createComponent(HtmlSelectManyMenu.COMPONENT_TYPE);
}
answerBinding = application.createValueBinding("#{pollPickerBacker.answers}");
}
answer.getChildren().add(getAnswerOptionItems());
}
answer.setId("answer");
answer.setValueBinding("value", answerBinding);
children.add(answer);
HtmlCommandButton submitButton =
(HtmlCommandButton)application.createComponent(HtmlCommandButton.COMPONENT_TYPE);
submitButton.setValue("Post your answer");
children.add(submitButton);
return null;
}
Note how we create a new ValueBinding for the expression #{pollPickerBacker.answers} for multiple answers and #{pollPickerBacker.answer} for singular answers. These bindings ensure that any value entered by the user in the answer item – being it an InputText or a RadioGroup or a List – is stored on the answer or answers property of the backing bean (and is also read from that property’s getter method when the page is rendered).
If we now for example change the question definition:
And click on the Rephrase Question button, the rephraseQuestionAction clears the PanelGrid, creates three OutputLabels – two for prompts, one for the question text- and adds either an InputText for an open answer, a radiogroup or select for a single answer or a set of checkboxes or a list for multiple answers:
Below you find the code of the utility method that takes the Answer Options textarea’s content and parses it into a UISelectItems component:
private UISelectItems getAnswerOptionItems() {
Application application =
FacesContext.getCurrentInstance().getViewRoot().findComponent();
UISelectItems selectItems =
(UISelectItems)application.createComponent(UISelectItems.COMPONENT_TYPE);
ArrayList options = new ArrayList();
// split answerOptions on Carriage Return into label,value pairs
StringTokenizer st = new StringTokenizer (getAnswerOptions());
while (st.hasMoreTokens ()) {
// split every row into label and key
StringTokenizer st2 = new StringTokenizer (st.nextToken(),",");
options.add(new SelectItem(st2.nextToken(), st2.nextToken()));
}
selectItems.setValue(options);
return selectItems;
}
The importance of this functionality to manipulate the pages in our own code lies largely in the options open to us to adopt an application to user preferences and even more interestingly: to drive part of our application from run-time meta-data. In this example, the user provides the meta-data inside the application. However, it seems more meaningful to have an application administrator or even a developer define such meta-data in some sort of maintenance application and use a more extended version of our backingbean in this code example as pre-render processor.
Resources
I am still a happy reader and user of Java Server Faces in Action by Kito Mann (Manning Publishing).
The sources for this article can be downloaded as JDeveloper 10.1.3 Application. Note that the sources can be used in any IDE, if you configure a project yourself: Chameleon.zip.
REALLY useful!
I used your techniques to build a page/bean combo that dynamically displays any table in a database, using <h:dataTable>, generated based on the table’s database metadata – sure beats one page per table!
In reply to my own comment, I have found that the best point to access the UIPanelGrid is in the setter from the binding. ie, if you have:
then in Bean.java you can have: setThePanel( UIPanel panel) { this.panel = panel; augmentPanel(); }
The panel that is passed in has nothing constructed on it and its id attribute is null, but you can still programmatically add components to it for it to render correctly.
In addition to this, if you have components added in the JSP also, they are preserved and merged with your programmatic additions.
Quite cool, really.
Regards,
Stewart
Hi Lucas,
This is an excellent article, and I thank you for writing it.
One curious situation I have, which I have been unable to solve, is that of programmatically creating the initial page.
I have tried various ways of accessing the created components, such as through a bean or PhaseListener.
It appears the page is not constructed at any point up to and including before RENDER_RESPONSE;
however, if I try programmatic manipulation after RENDER_RESPONSE, apparently the page has already been written to the browser,
so it’s there on a refresh, but not that first time.
Anyone have any ideas?
Thanks,
Stewart
Could you send me or post the source code in zip file?
Thanks!
Lucas, Could you please expand on what happens after one hits ‘Post your answer’? I am asking because while I am doing something similar and have been able to the ‘value’ of the selectItem but have not been able to successfully utilize getItemLabel. If you needed to access the label portion of the item selected as an ‘Answer’, how would you do it? Thanks, Jeanne
I was thinking about a printable version or a pdf dowload of the article. I will copy and paste the content of the article. Thanks.
Giuseppe, I am not entirely sure what you mean. All content is right there on the weblog. I have no Word document to back it up. You can just copy and paste it from the site – same as I would to provide this printable copy (at least if a Word document or something similar is what you are after). Is there anything in particular I can do for you?
Lucas, nice article as previous that I have seen in this blog. Is it possible to have a printable version of this article? Thanks in advance. Giuseppe
See for an interesting debate on this article: The Server Side – Thread 40395
Dmitry,
In this specific example, that would work. However, as I suggest in the article, there may be cases – especially when setting up a questionnaire style application – where the meta-data specifying the page’s look & feel are more maintained in a database and are much more voluminous and contain much more complicated – in part dynamically created – page fragment definitionis. It would be much harder to proactively anticipate al possible pieces of page elements (and their relative sequencing) in a JSP.
I agree that whenever a JSP can get you there with relatively simple (JSTL and EL) logic, it is preferable to defining the UI Components in code. But as soon as the JSP starts to grow fat with lots of if-conditions and repetitions of the same component definitions, resorting to code based run-time manipulation to me is certainly a valuable option.
By the way, how many lines of Servlet code would you need to achieve what this snippet of code does:
UIComponent answer = (HtmlSelectOneRadio)application.createComponent(HtmlSelectOneRadio.COMPONENT_TYPE);
ValueBinding answerBinding = application.createValueBinding(“#{pollPickerBacker.answer}”);
answer.getChildren().add(getAnswerOptionItems());
answer.setId(“answer”);
answer.setValueBinding(“value”, answerBinding);
children.add(answer);
You cannot make that comparison with out.println() too lightly: we are talking 7 lines of code vs. a very substantial number – in the Servlet approach you need to take care of so many things the UI Components in particular and JSF in general does for you.
So, depending on the choice we need to render the different views. And view in this example is actually generated in our Java code. Like old servlets: out.println(…)
Sure it will work, but I prefer to keep code for controls in JSP. It is just much more easy to update. So this condition could be checked in JSP for example:
if (“s”.equalsIgnoreCase(getAnswerStyle()))
Dmitry
http://www.servletsuite.com/