In this section, we discuss the syntax for value binding expressions in gruesome detail. This section is intended for reference. Feel free to skip it at first reading. Let us start with an expression of the form a.b. For now, we'll assume that we already know the object to which a refers. If a is an array, a list, or a map, then special rules apply see the next subsection. If a is any other object, then b must be the name of a property of a. The exact meaning of a.b depends on whether the expression is used in rvalue mode or lvalue mode. This terminology is used in the theory of programming languages to denote that an expression on the right-hand side of an assignment is treated differently from an expression on the left-hand side. Consider the assignment
left = right; A compiler generates different code for the left and right expressions. The right expression is evaluated in rvalue mode and yields a value. The left expression is evaluated in lvalue mode and stores a value in a location. The same phenomenon happens when you use a value binding expression in a user interface component:
<h:inputText value="#{user.name}"/> When the text field is rendered, the expression user.name is evaluated in rvalue mode, and the getName method is called. During decoding, the same expression is evaluated in lvalue mode, and the setName method is called. In general, the expression a.b in rvalue mode is evaluated by calling the property getter, whereas a.b in lvalue mode calls the property setter. Using Brackets Just as in JavaScript, you can use brackets instead of the dot notation. That is, the following three expressions all have the same meaning:
a.b a["b"] a['b'] For example, user.password, user["password"], and user['password'] are equivalent expressions. Why would anyone write user["password"] when user.password is much easier to type? There are a number of reasons. When you access an array or map, the [] notation is more intuitive. You can use the [] notation with strings that contain periods or dashes, for example, msgs["error.password"]. The [] notation allows you to dynamically compute a property: a[b.propname]. TIP | Use single quotes in value binding expressions if you delimit attributes with double quotes: value="#{user['password']}". Alternatively, you can switch single and double quotes: value='#{user["password"]}'. |
Map and List Expressions The value binding expression language goes beyond bean property access. For example, let m be an object of any class that implements the Map interface. Then m["key"] (or the equivalent m.key) is a binding to the associated value. In rvalue mode, the value
m.get("key") is fetched. In lvalue mode, the statement
m.put("key", right); is executed. Here, right is the "right-hand side" value that is assigned to m.key. You can also access a value of any object of a class that implements the List interface (such as an ArrayList). You specify an integer index for the list position. For example, a[i] (or, if you prefer, a.i) binds the ith element of the list a. Here i can be an integer or a string that can be converted to an integer. The same rule applies for array types. As always, index values start at zero. Table 2-3 summarizes these evaluation rules. Table 2-3. Evaluating the Value Binding Expression a.bType of a | Type of b | lvalue mode | rvalue mode |
---|
null | any | error | null | any | null | error | null | Map | any | a.put(b, right) | a.get(b) | List | convertible to int | a.set(b, right) | a.get(b) | array | convertible to int | a[b] | a[b] | bean | any | call setter of property with name b.toString() | call getter of property with name b.toString() |
CAUTION | Unfortunately, value bindings do not work for indexed properties. If p is an indexed property of a bean b and i is an integer, then b.p[i] does not access the ith value of the property. It is simply a syntax error. This deficiency is inherited from the JSTL expression language. |
Resolving the Initial Term Now you know how an expression of the form a.b is resolved. The rules can be applied repetitively to expressions such as a.b.c.d (or, of course, a['b'].c["d"]). We still need to discuss the meaning of the initial term a. In the examples, you have seen so far, the initial term referred to a bean that was configured in the faces-config.xml file, or to a message bundle map. Those are indeed the most common situations. But it is also possible to specify other names. There are a number of predefined objects. Table 2-4 shows the complete list. For example,
header['User-Agent'] Table 2-4. Predefined Objects in the Value Binding Expression LanguageVariable Name | Meaning |
---|
header | a Map of HTTP header parameters, containing only the first value for each name | headerValues | a Map of HTTP header parameters, yielding a String[]array of all values for a given name | param | a Map of HTTP request parameters, containing only the first value for each name | paramValues | a Map of HTTP request parameters, yielding a String[]array of all values for a given name | cookie | a Map of the cookie names and values of the current request | initParam | a Map of the initialization parameters of this web application. Initialization parameters are discussed in Chapter 10. | requestScope | a Map of all request scope attributes | sessionScope | a Map of all session scope attributes | applicationScope | a Map of all application scope attributes | facesContext | The FacesContext instance of this request. This class is discussed in Chapter 6 | view | The UIViewRoot instance of this request. This class is discussed in Chapter 7 |
is the value of the User-Agent parameter of the HTTP request that identifies the user's browser. If the initial term is not one of the predefined objects, the JSF implementation looks for it in the request, session, and application scopes, in that order. Those scopes are map objects that are managed by the servlet container. For example, when you define a managed bean, its name and value are added to the appropriate scope map. Finally, if the name is still not found, it is passed to the VariableResolver of the JSF application. The default variable resolver looks up managed-bean elements in a configuration resource, typically the faces-config.xml file. Consider, for example, the expression
#{user.password} The term user is not one of the predefined objects. When it is encountered for the first time, it is not an attribute name in request, session, or application scope. Therefore, the variable resolver processes the faces-config.xml entry.
<managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class>com.corejsf.UserBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> It calls the default constructor of the class com.corejsf.UserBean. Next, it adds an association to the sessionScope map. Finally, it returns the object as the result of the lookup. When the term user needs to be resolved again in the same session, it is located in the session scope. Composite Expressions You can use a limited set of operators inside value binding expressions: arithmetic operators + - * / %. The last two operators have alphabetic variants div and mod. relational operators < <= > >= == != and their alphabetic variants lt le gt ge eq ne. (The first four variants are required for XML safety.) logical operators && || ! and their alphabetic variants and or not. (The first variant is required for XML safety.) the empty operator. The expression empty a is true if a is null, an array or String of length 0, or a Collection or Map of size 0. the ternary ?: selection operator Operator precedence follows the same rules as in Java. The empty operator has the same precedence as the unary - and ! operators. Generally, you don't want to do a lot of expression computation in web pages that would violate the separation of presentation and business logic. However, occasionally the presentation layer can benefit from operators. For example, suppose you want to hide a component when the hide property of a bean is true. To hide a component, you set its rendered attribute to false. Inverting the bean value requires the ! (or not) operator:
<h:inputText rendered="#{!bean.hide}" ... /> Finally, you can concatenate plain strings and value binding expressions, simply by placing them next to each other. Consider, for example,
<h:outputText value="#{messages.greeting}, #{user.name}!"/> The statement concatenates four strings: the string returned from #{messages.greeting}, the string consisting of a comma and a space, the string returned from #{user.name}, and the string "!". You have now seen all the rules that are applied to resolve value binding expressions. Of course, in practice, most expressions are simply of the form #{bean.property}. Just come back to this section when you need to tackle a more complex expression. Method Binding Expressions A method binding expression denotes an object together with a method that can be applied to it. For example, here is a typical use of a method binding expression.
<h:commandButton action="#{user.checkPassword}"/> We assume that user is a value of type UserBean and checkPassword is a method of that class. The method binding expression is simply a convenient way of describing a method invocation that needs to be carried out at some future time. When the expression is evaluated, the method is applied to the object. In our example, the command button component will call user.checkPassword() and pass the returned string to the navigation handler. Syntax rules for method binding expressions are similar to those of value binding expressions. All but the last component are used to determine an object. The last component must be the name of a method that can be applied to that object. Four component attributes can take a method binding expression: action (see Chapter 3) validator (see Chapter 6) actionListener (see Chapter 7) valueChangeListener (see Chapter 7) The parameter and return types of the method depend on the context in which the method binding is used. For example, an action must be bound to a method with no parameters and return type String, whereas an actionListener is bound to a method with one parameter of type ActionEvent and return type void. The code that invokes the method binding is responsible for supplying parameter values and processing the return value. |