When we write custom
components, we need to be sure that we follow the right techniques. Only then
we can focus on implementing custom components with a "useful" behavior,
that are not just didactical examples. But, learning the right techniques is
not very simple because there are many aspects to keep in mind and many
examples on Internet doesn't really respect the written and unwritten (convensions)
rules.
An important aspect of
custom components involves managing their state. JSF 2.0 comes with the StateHelper interface,
which basically allows us to store, read, and remove data across multiple requests
(postbacks). This means we can use it to preserve states of the components.
This involves storing the component's attributes also. OmniFaces source code
provides us clear hints of how we should declare the attributes of a custom
components using the StateHelper
interface. OmniFaces, defines a helper class for StateHelper that uses generic type-inference
to make code that uses the StateHelper
slightly less verbose - personally, I prefer to use it instead of StateHelper. The
source code is listed below:
package org.omnifaces.util;
import java.io.Serializable;
import javax.faces.component.StateHelper;
public class State {
private
final StateHelper stateHelper;
public
State(StateHelper stateHelper) {
this.stateHelper
= stateHelper;
}
@SuppressWarnings("unchecked")
public
<T> T get(Serializable key) {
return
(T) stateHelper.eval(key);
}
@SuppressWarnings("unchecked")
public
<T> T get(Serializable key, Object defaultValue) {
return
(T) stateHelper.eval(key, defaultValue);
}
@SuppressWarnings("unchecked")
public
<T> T put(Serializable key, T value) {
return
(T) stateHelper.put(key, value);
}
}
Per example, we can
focus on the OmniFaces CommandScript
component, and notice that:
·
The attributes are declared in a private Java enum type - important
to notice that the set of predefined constants (the attributes) are declared in
lowercase, because they have to match the attribute names
private enum PropertyKeys {
name,
execute, render, onbegin, oncomplete;
}
private final State state = new
State(getStateHelper());
·
The third and the last step consist in declaring
the getters and the setters for the attributes. In the setters method,
OmniFaces uses the StateHelper.put()
method, as shown in the following code:
public void setName(String name) {
state.put(PropertyKeys.name,
name);
}
public void setExecute(String execute) {
state.put(PropertyKeys.execute,
execute);
}
public void setRender(String render) {
state.put(PropertyKeys.render,
render);
}
public void setOnbegin(String onbegin) {
state.put(PropertyKeys.onbegin,
onbegin);
}
public void setOncomplete(String oncomplete) {
state.put(PropertyKeys.oncomplete,
oncomplete);
}
These examples uses
behind the scene the StateHelper
method Object put(Serializable
key, Object value) method, but StateHelper also has a method Object put(Serializable key, StringmapKey,
Object value), which can be used to store values that would otherwise be
stored in a Map
instance variable. Moreover, StateHelper
has a method named void
add(Serializable key, Object value) that can be used to preserve values
which would otherwise be stored in a List instance variable.
For the getters,
OmniFaces uses the StateHelper methods, Object
eval(Serializable key) and Object eval(Serializable key, Object defaultValue). The
first one is wrapped in State as, Object get(Serializable key), and
the second one is wrapped in State as, Object get(Serializable key, Object defaultValue).
The later, search for the key and if it can't find it, then the default value (defaultValue) is
returned. This is a very useful approach because it spears us to perform null value checks.
Besides these methods, StateHelper
also has the Object
get(Serializable key) method.
public String getName() {
return state.get(PropertyKeys.name);
}
public String getExecute() {
return
state.get(PropertyKeys.execute, "@this");
}
public String getRender() {
return
state.get(PropertyKeys.render, "@none");
}
public String getOnbegin() {
return
state.get(PropertyKeys.onbegin);
}
public String getOncomplete() {
return
state.get(PropertyKeys.oncomplete);
}
Notice that for the
attributes that comes with default values, OmniFaces uses the StateHelper method Object eval(Serializable
key, Object defaultValue), while for those that doesn't need a default
value (do not understand that an attribute that doesn't have a default value,
cannot be required; the name
attribute is required!), OmniFaces uses the StateHelper method Object StateHelper.eval(Serializable key).
In order to remove an
entry from StateHelper,
we can call the StateHelper
method Object remove(Serializable
key) or Object
remove(Serializable key,Object valueOrKey).
If an attribute is
required, you have to perform a specific check. One place where this check can
take place is in the custom component renderer. Per example, the CommandScript
component has the required name
attribute, and the check is performed in the encodeBegin() method, like this:
@Override
public void encodeBegin(FacesContext context)
throws IOException {
...
String
name = getName();
if
(name == null) {
throw
new IllegalArgumentException(ERROR_MISSING_NAME);
}
...
Moreover, here it is a
good place to check if the provided value respects a pattern:
...
private
static final Pattern PATTERN_NAME = Pattern.compile("[$a-z_][$\\w]*",
Pattern.CASE_INSENSITIVE);
...
if
(!PATTERN_NAME.matcher(name).matches()) {
throw new
IllegalArgumentException(String.format(ERROR_ILLEGAL_NAME, name));
}
...
In some cases, these checks
can be performed via ComponentHandler.getRequiredAttribute() method,
which will perform a check at runtime and will throw a TagException.
Niciun comentariu :
Trimiteți un comentariu