Great new features in RichFaces 3.2: Inplace input and select
July 12th, 2008
Introduction
If you're using JBoss Seam to build sites, you're probably using the RichFaces components which are automatically included in Seam-Gen projects. RichFaces provides good-looking components which satisfy most of the Web 2.0 and AJAX needs of developers. RichFaces is great because it lets developers create complex AJAX interactions with tags which are as simple as plain old HTML tags. You want to create a toggle panel, for example? Put this in:
<rich:simpleTogglePanel switchType="client"
label="A RichFaces simpleTogglePanel">
This is a simple Web 2.0 type effect created using
RichFaces. As you can see, this component is just
as easy to create as any ordinary HTML.
</rich:simpleTogglePanel>
and the result is:
RichFaces 3.2 added two particularlly great new input components: rich:inplaceInput and rich:inplaceSelect. We'll show how to use these great inputs, and we'll also create a new textarea input that uses the same input style.
rich:inplaceInput and rich:inplaceSelect
The documentation gives all the details on these components. I'll just show my favorite style of using them. Try it out, by double-clicking:
Use the small controls that pop up to the right to set the value, or to cancel the change. Try setting it to a blank value to see a validation error.
We use an A4J support component to AJAX-submit:
<h:form>
<rich:panel style="width:6cm;"
id="inplace-demo-panel">
<rich:inplaceInput id="testInput" layout="block"
value=""
required="true"
showControls="true"
changedHoverClass="hover"
viewHoverClass="hover"
viewClass="inplace"
changedClass="inplace"
selectOnEdit="true"
editEvent="ondblclick">
<a:support
event="onviewactivated"
reRender="inplace-demo-panel"/>
</rich:inplaceInput>
<h:message style="color: red;"
for="testInput" />
</rich:panel>
</h:form>
This is a great component of a form. You can display the values in a panel layout, and if the user wants to change a value, he double-clicks on it, enters it, and hits the submit control. It keeps your display looking like a display of information, not like a web form, and it provides a good way for a user to selectively edit one field at a time.
There's a select-one counterpart to the rich:inplaceInput field. Try this:
The source is similar to the text input:
<h:form>
<rich:panel id="selectPanel" style="width:5cm;">
<rich:inplaceSelect value=""
openOnEdit="true"
showControls="true"
editEvent="ondblclick"
layout="block"
viewClass="inplace"
changedClass="inplace"
changedHoverClass="hover"
defaultLabel="Please select"
viewHoverClass="hover">
<f:selectItem itemValue="1" itemLabel="Spicy"/>
<f:selectItem itemValue="2" itemLabel="Medium"/>
<f:selectItem itemValue="3" itemLabel="Mild"/>
<f:selectItem itemValue="4" itemLabel="None"/>
<a:support
event="onviewactivated"
reRender="selectPanel"/>
</rich:inplaceSelect>
</rich:panel>
</h:form>
What about text areas?
These are great, but what about text areas? Unfortunately, there is no textarea input component.
What we want is a text area that allows this in-place style of editting. It should also accept Seam text, and show Seam text validation messages. Fortunately, with a little bit of JavaScript, we can build this.
First, here's what it looks like in action. Go ahead and double-click to edit. Make some changes. Use some Seam text. Make a Seam text error and click the "submit" control to see the validation error. An easy error is to leave an unclosed format directive, such as a single =.
The time now is: Mon Mar 15 06:10:35 PDT 2010
This is the nice way to do text input. How does it work?
Building the inplace edit textarea
Download the source: richfaces-sample-code.zip. This file contains the XHTML for the component, a test page that uses it, and the simple Seam component that it interacts with.
The display of the text is simple. We put it in a rich:panel, using s:formattedText to render the Seam text. This shows how easy it is to attach a JavaScript function to any RichFaces component:
<rich:panel id="displayDiv" ondblclick="showEdit();" >
<s:formattedText value=""/>
</rich:panel>
This is the function that happens when the user double-clicks:
function showEdit() {
document.getElementById("editDiv").style.display="block";
document.getElementById("displayDiv").style.display="none";
}
All it does is switch the editDiv display
style to block and sets the
displayDiv style to none, hiding it.
Now let's look at the editDiv:
<rich:panel id="editDiv" style="display:none;">
<h:form id="editForm">
<h:inputTextarea
id="editArea" value=""
style="width:100%;">
<s:validateFormattedText />
</h:inputTextarea>
<div style="text-align:right;margin-top:-0.5cm;">
<a:commandButton style="background-color:#ECF4FE;border:solid;border-width:1px;"
image="/components/ok.png"
value="save" reRender="displayDiv,savedText,textError"
oncomplete="hideEditIfValid();"/>
<h:graphicImage
onclick="cancelEdit();"
url="/components/cancel.png"
style="background-color:#ECF4FE;margin:0px;padding:0px;border:solid;border-width:1px;cursor:pointer;"/>
</div>
</h:form>
<s:div id="textError">
<h:message for="editArea" styleClass="error"/>
</s:div>
<div style="clear:both;"/>
</rich:panel>
We have a basic h:inputTextarea
with a s:validateFormattedText. That's the
universal way of doing Seam text input.
We've also created a div to hold the controls.
We've right-aligned it and bumped it up by 0.5cm
with a negative margin-top setting.
Within the controls div we have two controls:
one to save the changes and the other to cancel the changes.
Here's the save changes button:
<a:commandButton style="background-color:#ECF4FE;border:solid;border-width:1px;" image="/components/ok.png" value="save" reRender="displayDiv,savedText,textError" oncomplete="hideEditIfValid();"/>
This is an A4J commandButton, which allows
an AJAX-style submit and re-rendering of selected components.
We want it to rerender three components:
displayDivtextErrorsavedText
Why we re-render displayDiv and
textError is clear. savedText
is more interesting:
<h:inputTextarea id="savedText"
style="display:none;"
value=""/>
The purpose of savedText is to hold
the value of the text before the user edits it. The
save button must re-render savedText
when the value binding changes, so that the
savedText will always reflect the last
successfully saved text. Note that if validation
fails (ie, there is a Seam text error)
savedText is not updated because the
apply values phase doesn't succeed, so the value
binding is not changed.
There's a key bit of JavaScript on this a:commandButton:
oncomplete="hideEditIfValid();". The
oncomplete event is triggered after
the apply values phase is complete and the UI has been re-rendered
on the browser. This is your hook for executing JavaScript
after RichFaces and JSF are done with their work.
And we'll use this hook to decide which divs to display.
If the save was successful, we want to hide the edit div and
the error div. If the save was not successful, there was a validation
error, and we want to show the error and the edit div, and hide
the display div. Here's the function that does it:
function hideEditIfValid() {
// check to see if the error div is
// empty or not. It would be nice
// if there were a RichFaces function
// to check a component for errors, but
// it works fine to do it this way.
if(document.getElementById("textError").hasChildNodes()) {
// re-hide the display, and let the editing continue
document.getElementById("displayDiv").style.display="none";
return;
}
// The update was valid, so hide the edit div.
document.getElementById("displayDiv").style.display="block";
document.getElementById("editDiv").style.display="none";
}
Note that within JSF you can check to see if there are Faces messages using this idiom:
rendered="#{facesContext.maximumSeverity==null}"
In this case, we did it within JavaScript, so we checked for child elements. Could the idiom above be used instead? That's left as an excercise for the reader.
Now on to the cancel control:
<h:graphicImage onclick="cancelEdit();" url="/components/cancel.png" style="background-color:#ECF4FE;margin:0px;padding:0px;border:solid;border-width:1px;cursor:pointer;"/>
First, why isn't this an HTML button? I tried
using a button first, putting an onclick event
binding on it. It worked as epxected. Unfortunately, the different
browsers render to button in significantly
different ways.
For instance, FireFox 3 added left and right padding in the
button even if I set padding:0px;.
Konqueror didn't do that. I don't understand this. I tried
a few things, but couldn't make something that was clean and
cross-browser. Rather than fight that, it was easiest
to turn an image into a button, using the right CSS
and JavaScript. The key is the onclick event
listener, to detect when the user clicks, and the
cursor:pointer; style, to give the user
the visual cue that this is a clickable button. With those
two elements in place, it is a button in every respect,
except it doesn't go to a "depressed" look when clicked.
In this case, the button disappears as soon as it is clicked,
so no depressed button image is needed. If one were needed,
that could also be done with JavaScript. We have built our
own faux button control, without using a button.
Second, how does this cancel button work? It has
an onclick listener which calls the cancelEdit()
function:
function cancelEdit() {
document.getElementById("editForm:editArea").value =
document.getElementById("savedText").value;
document.getElementById("displayDiv").style.display="block";
document.getElementById("editDiv").style.display="none";
var errorNode = document.getElementById("textError");
if(errorNode.hasChildNodes())
while (errorNode.childNodes.length >= 1)
errorNode.removeChild(errorNode.firstChild);
return false;
}
This function grabs the saved text value
from the hidden textarea,
and saves it to the visible textarea.
Also, because we're cancelling the edit, any validation
errors can be cleared out. Invalid input is never
bound to the value binding, so the errors that
were there are irrelevant. We clear the error
div by simply removing all the child nodes.
And of course we need to change the display
style settings to make the view div visible
display:block;and the
edit div invisible (display:none;).
Conclusion
We have used the two new inplace components that come with RichFaces 3.2. We have also built a component for Seam which allows inplace style editing of a textarea, with Seam text validation. These components are a great way to do a user interface. Check back later, and we'll put up screen shots of these components being used in actual applications.
If you want to learn JBoss Seam
We're offering a JBoss Seam seminar where you can accelerate yourself and your team and start building great web applications with all the power of RichFaces and JBoss Seam.
Download the source for this article
Download the source: richfaces-sample-code.zip.