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







Reply via email to