Previous Section  < Day Day Up >  Next Section

12.4 Programmatically Modifying Components

In all examples you've seen so far, component property values are pulled in from a bean, like here:

<h:commandButton value="New" disabled="#{reportHandler.newDisabled}" />

I personally like this model, because it provides a clean separation between application logic and user interface components, but it's not the only model supported by JSF. Some people are used to manipulating component properties programmatically instead. For instance, say you want to highlight an invalid value by turning the text in an input field red and adding a custom error message next to the field. To do so programmatically, you need references to the input component and the output component that holds the error message. Then you need to call the appropriate property setter method on the components.

The most convenient and easiest way to get a reference to a JSF component is using a component binding. A component binding is a special type of value binding that JSF uses to bind a component instance to a property in a bean. Here's an example:

<%@ page contentType="text/html" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<f:view>

  <html>

    <body bgcolor="white">

      <h:form>

        Everything but "the kitchen sink": 

        <h:inputText validator="#{myBean.validateText}" />

        <h:outputText binding="#{myBean.errorComp}" />

        <br>

        <h:commandButton value="Test" />

      </h:form>

    </body>

  </html>

</f:view>

This page contains a form with an input component, an output component, and a command button. If you enter the value "the kitchen sink" as the input value and click the button, a custom validator calls property setter methods on the input and output components to display an error message and turn the input red.

The binding attribute for the <h:outputText> action element contains a component binding expression pointing to the errorComp property in a managed bean named myBean:

package com.mycompany.beans;



import javax.faces.component.UIComponent;

import javax.faces.component.html.HtmlOutputText;

import javax.faces.context.FacesContext;



public class MyBean {

    private HtmlOutputText errorComp;



    public void setErrorComp(HtmlOutputText errorComp) {

        this.errorComp = errorComp;

    }



    public HtmlOutputText getErrorComp( ) {

        return errorComp;

    }

    ...

JSF calls the setter method when the component is first created and during the Restore View phase for all subsequent request. The reference is therefore always initialized when the bean methods are invoked.

The getter method is more controversial. It may return a component instance created by the bean, possibly preconfigured, depending on runtime conditions. The JSF actions call the getter method when the component is about to be created; if it returns a component instance instead of null, the returned instance is added to the component tree. This can be handy for complex configuration cases. One example is a UIData component that needs to be configured with different UIColumn children depending on runtime conditions, say, for an application that supports ad-hoc database queries. But the getter method can also cause confusion if misused—the returned component may be a different type of component than what the action element represents. I recommend that you use this feature only if you absolutely have to.

With the component binding in place, any method in the same bean (such as the validator method, used in this example) has easy access to the component. The validator attribute lets you bind a component to a validator method in a bean instead of using a separate validator instance attached to the component, as we did in Chapter 7. It's an alternative that makes sense when the validation rules apply only to a specific component instance:[2]

[2] Because the validator attribute is easier to use than the <f:validator> action element, you may want to use it even for common validation needs. One way to do this is to create a managed bean that contains only validation methods that you can bind to from any component.

    public void validateText(FacesContext context, UIComponent comp,

      Object value) {

      if ("the kitchen sink".equals(value)) {

          errorComp.setValue("I said everything but!");

          errorComp.setStyle("color: red");

          comp.getAttributes( ).put("style", "color: red");

      }

      else {

          errorComp.setValue(null);

          errorComp.setStyle(null);

          comp.getAttributes( ).put("style", null);

      }

    }

}

The validator method takes a FacesContext, a UIComponent, and an Object containing the value as arguments, and it has a void return type.

The validateText() method compares the component's value to the string "the kitchen sink". If it's a match, the method sets the value of the output component to an error message and the style property to a CSS style that turns the text red.

Note that the data type of the errorComp property in this example is javax.faces.component.html.HtmlOutputText. This is a subclass of the generic UIOutput component class, providing type-safe property accessor methods for all HTML-specific properties the text renderer in the default HTML render kit supports. As you may recall, JSF components can be used with different renderers, so the generic component classes don't have property accessor methods for render-dependent things like CSS styles or field width. To make it slightly easier to work with the combinations of generic components and the HTML renderers defined by JSF, the specification defines a concrete component class for each combination and requires that the JSP component actions create instances of the corresponding concrete class. This makes it possible to use the concrete HTMLOutputText class as the component binding property type and, hence, to set the CSS style for the output component by calling the setStyle() method.

Contrast this with how the CSS style for the input component is set in the validateText() method. The input component reference is one of the method arguments, so no component binding is needed, but the argument type is the generic UIComponent class. For a generic component class, render-dependent things are set as generic attributes that the renderer then reads. In this example, we know that the input component is in fact an instance of the concrete HtmlInputText class, so an alternative to setting the style as an attribute is to cast the argument to this type and call the type-safe setStyle() method instead.

Attribute-Property Transparency

To make life more interesting, and also easier on renderers, JSF provides what's called attribute-property transparency in the specification. The transparency is achieved by giving the java.util.Map returned by the getAttributes() method special qualities—if the key passed to the get() and put() methods matches the name of a bean-style property of the component class, the Map uses the property accessor method to read or write the value; otherwise the value is handled as a regular Map entry. You can therefore access both render-dependent attributes (such as style attributes) and render-independent properties (such as a component's value) as if they were all generic attributes, which avoids a lot of typecasting in renderers.

As an example, a renderer can get the value of the submittedValue property from a UIComponent reference without casting the reference to EditableValueHolder (which is the interface that defines the accessor methods for the submittedValue property), like this:

public void encodeBegin(FacesContext context,

    UIComponent component) throws IOException {

    ...

    Object submittedValue =

        component.getAttributes( ).get("submittedValue");

    ...
}


As an alternative to using a component binding to get hold of a component, there's a findComponent( ) method you can use if you know the component's ID. The bean can be rewritten like this to use this approach instead:

package com.mycompany.beans;



import javax.faces.component.UIComponent;

import javax.faces.component.html.HtmlOutputText;

import javax.faces.context.FacesContext;



public class MyBean {

    public void validateText(FacesContext context, UIComponent comp,

      Object value) {



      HtmlOutputText errorComp = (HtmlOutputText)

          comp.findComponent(comp.getId( ) + "Error");



      if ("the kitchen sink".equals(value)) {

          errorComp.setValue("I said everything but!");

          errorComp.setStyle("color: red");

          comp.getAttributes( ).put("style", "color: red");

      }

      else {

          errorComp.setValue(null);

          errorComp.setStyle(null);

          comp.getAttributes( ).put("style", null);

      }

    }

}

Here I assume that the ID for the output component is the same as for the input component plus "Error", so you must add the corresponding id attributes for both components in the JSP page.

The findComponent() argument value is a string that identifies a component. In the simplest case, it's just a component ID. This works fine if the component you're looking for is part of the same naming container as the component that you call the method on (or the naming container itself). Otherwise, you need to use either a relative or an absolute path, where a colon serves as a path element separator.

An absolute path starts with a colon; for example, ":myForm:myOutput" means "start at the component tree root and locate a naming container with the ID myForm, and then locate a component with the ID myOutput in that naming container." A relative path doesn't start with a colon; for example, "myForm:myOutput" means "unless the component I'm calling this method on is a naming container, search for a parent that is or the view root is found. Using the root or the naming container as the starting point, locate a naming container with the ID myForm, and then locate a component with the ID myOutput in that naming container."

Only three of the standard component classes implement the NamingContainer interface (act as naming containers): UIForm, UIData, and UINamingContainer (created by the <f:subview> action); it's only for children of these component types that you need to worry about the more complex findComponent() argument syntax.

Whether to use component bindings or the findComponent( ) method is a matter of taste. I prefer to avoid the whole issue and configure the components in the JSP page by pulling values from a bean instead, as shown in most of the other examples in this book.

    Previous Section  < Day Day Up >  Next Section