Le Dec 1, 2004, � 9:42 PM, David Geary a �crit :
[snip]
Hmmm, sounds like I have to have a getter method for each form
field
on my managed bean? What a pain?
Isn't there a way to look up if the field is required and just add
a * - a solution that's generic for all outputLabel's? I'm willing
to hack source code or create components if I need to - I just need
to know 2 things:
1. Yes/No - if I have to hack the source to make the dynamic
lookup
possible?
No hacking is required.
2. Where do I hack the source? I'm guessing there's some way to
lookup the validation metadata for the inputs and use that data in
the labels.
You could implementing a replacement for the Label renderer:
1. In a renderer method such as encodeBegin(), get the value of the
renderer's component (encodeBegin is passed the component. Call the
component's getValue method). In this case, the component is the
label and the value is the id of the labeled component.
2. Get a reference to the labeled component. In a JSF component
hierarchy, any component can find any other component in the
hierarchy with the findComponent(String) method, where the String is
the component's id. So the label can find the labeled component.
3. Find out if the labeled component is required. Input components
implement the editableValueHolder interface, which defines an
isRequired method.
4. Render according to whether the labeled component is required.
david
I went ahead and implemented this. After replacing the standard Label
renderer, this...
<h:outputLabel for="name" value="#{msgs.namePrompt}"/>
<h:inputText id="name" value="#{registerForm.name}" />
...has a plain prompt, whereas the following has an asterik prepended
to the prompt:
<h:outputLabel for="name" value="#{msgs.namePrompt}"/>
<h:inputText id="name" value="#{registerForm.name}" required="true"/>
Here's how it works. First, add this to your faces config file:
<faces-config>
...
<render-kit>
<description>Some replacements for the standard
renderers</description>
<renderer>
<description>Replacement renderer for
h:outputLabel</description>
<component-family>javax.faces.Output</component-family>
<renderer-type>javax.faces.Label</renderer-type>
<renderer-class>renderers.LabelRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
Because we didn't specify a renderkit name, JSF modifies the default
renderkit by replacing the javax.faces.Label type renderer with our
custom version.
Here's the renderer class:
package renderers;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
// Renderer for the Label components
public class LabelRenderer extends Renderer {
public boolean getRendersChildren() {
return false;
}
public void encodeBegin(FacesContext context, UIComponent
component)
throws java.io.IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("label", component);
String styleClass = (String)
component.getAttributes().get("styleClass");
if (styleClass != null)
writer.writeAttribute("class", styleClass, null);
Map attrs = component.getAttributes();
writer.writeAttribute("for", component.getClientId(context),
null);
UIInput input =
(UIInput)component.findComponent((String)attrs.get("for"));
if(input.isRequired())
writer.write("*");
writer.write(attrs.get("value").toString());
}
public void encodeEnd(FacesContext context, UIComponent component)
throws java.io.IOException {
ResponseWriter writer = context.getResponseWriter();
writer.endElement("label");
}
}
What's cool about this is that all h:outputLabel tags will be fitted
with our custom renderer, so by modifying the config file and
implementing the renderer, we are changing the behavior of existing
JSP pages without modifying the pages themselves. All labels that
decorate required fields will be prepended with asteriks.
Notice that my simple renderer is not industrial strength. It does
not
account for all the h:outputLabel attributes, nor does it allow a
nested component. But it's not too bad for 20 minutes of work.
btw, it's easy to add an enhancement so that the asterik is red if
the
corresponding field failed validation. Here's the modified
encodeBegin
method of the renderer:
...
public void encodeBegin(FacesContext context, UIComponent component)
throws java.io.IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("label", component);
String styleClass = (String)
component.getAttributes().get("styleClass");
if (styleClass != null)
writer.writeAttribute("class", styleClass, null);
Map attrs = component.getAttributes();
writer.writeAttribute("for", component.getClientId(context),
null);
UIInput input =
(UIInput)component.findComponent((String)attrs.get("for"));
if(input.isRequired()) {
boolean msgs = hasMessages(context, input);
if(msgs) {
writer.startElement("font", null);
writer.writeAttribute("color", "red", null);
}
writer.write("*");
if(msgs) {
writer.endElement("font");
}
}
writer.write(attrs.get("value").toString());
}
private boolean hasMessages(FacesContext context, UIComponent
component) {
Iterator it = context.getClientIdsWithMessages();
boolean found = false;
while(it.hasNext()) {
String id = (String)it.next();
if(component.getClientId(context).equals(id))
found = true;
}
return found;
}
...
david
Thanks,
Matt
Do I have to subclass the existing JSP Tag to do this?
You hardly ever want to subclass an existing component tag,
because
tags are really just thin veneers for component/renderer pairs.
You
could, however, implement your own Label renderer and plug it into
h:outputLabel. But I would opt for one of the easier solutions
above.
2. Is it possible to auto-add a colon? I'm able to do this
pretty
easily with a custom taglib and Commons Validator, but it seems
difficult with MyFaces? With Tapestry, I can subclass the
existing component and add my own suffix (including a space
before
the colon for the French locale).
The same techniques for prepending an asterik will work for
appending a colon. Again, you could implement your own Label
renderer that does anything you want.
david
Thanks,
Matt