On Wed, 1 Dec 2004 23:52:54 -0700, Matt Raible <[EMAIL PROTECTED]> wrote:
> Thanks David - much appreciated. Unfortunately, in my testing the
> "input" is always null b/c the label comes before the component it's
> for. If I move the label after the field, then the asterisk renders
> properly. Am I missing something?
No, you're not :-(. This is one of the interoperability issues
between JSF and JSP that we couldn't find a good solution for in a JSF
1.0 time frame.
The issue is that, on the *first* rendering of a page, the component
tree is built on the fly, in the order that the tags are evaluated.
On a subsequent rendering of the same page, the component tree will
already be there, and the asterisk will work as expected; it's only
the first time that there is an issue.
Craig
>
> Thanks,
>
> Matt
>
>
>
> David Geary wrote:
>
> > 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
> >>>>>
> >>>>
> >>>
> >>
> >
>
>