The <h:selectOneMenu> renders an HTML "select one" element
Common/basic usage in JSF (I) - using hard-coded <f:selectItem>:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>JSF selectOneMenu examples</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}">
<f:selectItem
itemValue="Rafael Nadal" itemLabel="Rafa" />
<f:selectItem
itemValue="Roger Federer" itemLabel="Roger F" />
<f:selectItem
itemValue="Novak Djokovic" itemLabel="Nole" />
</h:selectOneMenu>
<h:commandButton
value="Select" action="#{playerBean.selectedAction()}"/>
</h:form>
</h:body>
</html>
Common/basic usage in JSF (II) -
"populate" <f:selectItems> from a Map
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>JSF selectOneMenu examples</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}">
<f:selectItems
value="#{playerBean.playersMap}"/>
</h:selectOneMenu>
<h:commandButton
value="Select" action="#{playerBean.selectedAction()}"/>
</h:form>
</h:body>
</html>
Common/basic usage in JSF (III) -
"populate" <f:selectItems> from an array of Object
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>JSF selectOneMenu examples</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}">
<f:selectItems
value="#{playerBean.playersArray}" var="t"
itemLabel="#{t.label}" itemValue="#{t.value}"/>
</h:selectOneMenu>
<h:commandButton
value="Select" action="#{playerBean.selectedAction()}"/>
</h:form>
</h:body>
</html>
The <h:selectOneMenu> will be rendered in
HTML as:
<select size="1"
name="j_idt6:j_idt7">
<option
value="Rafael Nadal">Rafa</option>
<option
value="Roger Federer">Roger F</option>
<option
value="Novak Djokovic">Nole</option>
</select>
The PlayerBean will be:
package beans;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import java.util.LinkedHashMap;
import java.util.Map;
@Named
@SessionScoped
public class PlayerBean implements Serializable {
private static final long serialVersionUID = 1L;
private String selectedPlayer;
private static final Player[] playersArray;
private static final Map<String, Object> playersMap;
static {
playersArray = new Player[3];
playersArray[0] = new Player("Rafa", "Rafael
Nadal");
playersArray[1] = new Player("Roger F", "Roger
Federer");
playersArray[2] = new Player("Nole", "Novak Djokovic");
}
static {
playersMap = new LinkedHashMap<>();
playersMap.put("Rafa", "Rafael Nadal"); //label,
value
playersMap.put("Roger F", "Roger Federer");
playersMap.put("Nole", "Novak Djokovic");
}
public Map<String, Object> getPlayersMap() {
return playersMap;
}
public Player[] getPlayersArray() {
return playersArray;
}
public String getSelectedPlayer() {
return selectedPlayer;
}
public void setSelectedPlayer(String selectedPlayer) {
this.selectedPlayer = selectedPlayer;
}
public String selectedAction() {
return "data";
}
}
The Player simply encapsulate an item label and
value (used in case III from above):
package beans;
import java.io.Serializable;
import java.util.Objects;
public class Player implements Serializable {
private String label;
private String value;
public Player(String label, String value) {
this.label = label;
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int hashCode() {
int hash = 5;
hash = 29 * hash +
Objects.hashCode(this.label);
hash = 29 * hash +
Objects.hashCode(this.value);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Player other = (Player) obj;
if (!Objects.equals(this.label, other.label))
{
return false;
}
if (!Objects.equals(this.value, other.value))
{
return false;
}
return true;
}
@Override
public String toString() {
return label + "/" + value;
}
}
The data.xhtml page is:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title></title>
</h:head>
<h:body>
<!-- View submitted data
-->
Selected player: <br/>
#{playerBean.selectedPlayer}<br/>
</h:body>
</html>
Data flow in image:
More examples:
Add xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" for pass-through attributes
Disabled item using 'itemDisabled'
attribute
<h:form>
<h:selectOneMenu
value="#{playerBean.selectedPlayer}">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" itemDisabled="true"
/>
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Styling messages with inner CSS
<h:form>
<h:selectOneMenu
value="#{playerBean.selectedPlayer}" style="color:#00f;">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Styling messages with CSS class
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}"
styleClass="selectOneMenu-css">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Add a no selection option
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}">
<f:selectItem
itemLabel="Select a player ..."
noSelectionOption="true"/>
<f:selectItems value="#{playerBean.playersArray}"
var="t" itemLabel="#{t.label}"
itemValue="#{t.value}"/>
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Preselect an item via the bean's (post)
constructor
<h:form>
<h:selectOneMenu
value="#{playerBean.selectedPlayer}">
<f:selectItems
value="#{playerBean.playersArray}" var="t"
itemLabel="#{t.label}" itemValue="#{t.value}"/>
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
And place initialization in the bean constructor:
@Named
@SessionScoped
public class PlayerBean implements
Serializable {
...
public PlayerBean() {
selectedPlayer = "Roger Federer";
}
...
}
Or in post constructor (this is useful if you need
to perform initialization based on some injected artifacts):
@Named
@SessionScoped
public class PlayerBean implements
Serializable {
@Inject
private
SelectedPlayerBean selectedPlayerBean;
...
@PostConstruct
public void init() {
selectedPlayer = selectedPlayerBean.getPlayer();
}
...
}
Keep in mind that
constructor (and post constructor) are invoked at each
request in case of request scoped beans, so initialization will take place at
each request ! This kind of initialization is commonly useful in view/session
scoped beans where constructor (and post constructor) are invoked per
view/session. Notice below how second item was preselected:
Use a built-in converter
<h:form>
<h:selectOneMenu value="#{playerBean.selectedRank}"
converter="javax.faces.Integer">
<f:selectItems
value="#{playerBean.playersRanks}"/>
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
The story behind this example is pretty simple and
we start by supposing that the above converter is not specified. Basically, the
playersRanks
is a list of integers that "populates" our list, and the selectedRank
represents the user selections:
private Integer selectedRank;
private static final ArrayList<Integer>
playersRanks;
static {
playersRanks = new ArrayList<>();
playersRanks.add(1);
playersRanks.add(2);
playersRanks.add(3);
}
public ArrayList<Integer> getPlayersRanks() {
return playersRanks;
}
public Integer getSelectedRank() {
return selectedRank;
}
public void setSelectedRanks(Integer selectedRank) {
this.selectedRank = selectedRank;
}
So, the user may select the rank and submit it
without issues/errors. Even if no error occurred, we can notice a
"strange" behavior if we try to run the following snippet of code:
#{playerBean.selectedRank.getClass()}
The output reveals that the selected rank is a
string, not integer as we expected to see:
class java.lang.String
The explanation relies on the fact that "the
generic type information of List<Integer> is lost during runtime
and therefore JSF/EL who sees only List is not able to identify that the
generic type is Integer and assumes it to be default String
(as that's the default type of the underlying HttpServletRequest#getParameter()
call during apply request values phase) - BalusC".
There are two approaches:
·
explicitly specify a Converter
·
use Integer[]
instead
In this case, we can use the built-in javax.faces.Integer
built-in converter. Now, we can perform the same test and the output will be:
class java.lang.Integer
Read further explanations here.
Use a custom converter
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayerObj}"
converter="playerConverter">
<f:selectItems
value="#{playerBean.playersList}" var="t"
itemLabel="#{t.label}" itemValue="#{t}"/>
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
This use case continue the story from the above
use case. Basically, this time we use data for which we don't have a built-in
converter available. The custom converter used here is needed because this time
the list is "populated" with several Player instances:
private Player selectedPlayerObj;
private static final ArrayList<Player>
playersList;
static {
playersList = new ArrayList<>();
playersList.add(new Player("Rafa",
"Rafael Nadal"));
playersList.add(new Player("Roger
F", "Roger Federer"));
playersList.add(new Player("Nole",
"Novak Djokovic"));
}
public ArrayList<Player> getPlayersList() {
return playersList;
}
public Player getSelectedPlayerObj() {
return selectedPlayersList;
}
public void setSelectedPlayerObj(Player
selectedPlayerObj) {
this.selectedPlayerObj = selectedPlayerObj;
}
Remember from the above use case that the generic
type of List<>
is lost during runtime. Since the selected item is treated as string instead of
Player
instance, the below code will cause an error because #{playerBean.selectedPlayerObj.label}
cannot be evaluated:
#{playerBean.selectedPlayerObj.label} #{playerBean.selectedPlayerObj.value}
Since there is no built-in converter for
converting strings to Player instances, we need a custom converter as below:
package beans;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
@FacesConverter("playerConverter")
public class PlayerConverter implements Converter {
@Override
public Object getAsObject(FacesContext
context, UIComponent component,String value) {
String[] parts = value.split("/");
return new Player(parts[0],parts[1]);
}
@Override
public String getAsString(FacesContext
context, UIComponent component,Object value) {
return value.toString();
}
}
Now, everything works as expected!
Use the
omnifaces.SelectItemsConverter/omnifaces.SelectItemsIndexConverter converters
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayerObj}"
converter="omnifaces.SelectItemsConverter">
<f:selectItems value="#{playerBean.playersList}"
var="t" itemLabel="#{t.label}"
itemValue="#{t}"/>
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayerObj}"
converter="omnifaces.SelectItemsIndexConverter">
<f:selectItems
value="#{playerBean.playersList}" var="t"
itemLabel="#{t.label}" itemValue="#{t}"/>
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
If you read the above two use cases (recommended)
then is pretty easy to intuit that you will to write a custom converter for
each type of data that "populates" a list. Well, a generic solution
consist in using the OmniFaces, SelectItemsConverter
or SelectItemsIndexConverter.
The OmniFaces Showcase provides
all the information needed to understand how to use them, so go ahead.
Use HTML 5 'required' attribute
(specifies that the user is required to select a value before
submitting the form)
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}"
pt:required="true"
required="true">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Notice that this is a client side validation, so
reinforce it with a JSF server-side validation (e.g. required built-in
validator).
Use HTML 5 'autofocus' attribute
(specifies that the drop-down list should automatically get
focus when the page loads)
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayer}"
pt:autofocus="true">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak Djokovic"
itemLabel="Nole" />
</h:selectOneMenu>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Use 'valueChangeListener' attribute with
<h:selectOneMenu>
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayerObj}"
onchange="this.form.submit();"
converter="playerConverter" valueChangeListener="#{playerBean.selectedVCLAction}">
converter="playerConverter" valueChangeListener="#{playerBean.selectedVCLAction}">
<f:selectItems
value="#{playerBean.playersList}" var="t" itemLabel="#{t.label}"
itemValue="#{t}"/>
</h:selectOneMenu>
</h:form>
The selectedVCLAction() is listed below:
private static final Logger LOG =
Logger.getLogger(PlayerBean.class.getName());
public void selectedVCLAction(ValueChangeEvent
event) {
LOG.log(Level.INFO, "selectedVCLAction()
called, {0}", ((Player)event.getNewValue()));
}
AJAXify <h:selectOneMenu> with
'valueChangeListener' attribute
<h:form>
<h:selectOneMenu value="#{playerBean.selectedPlayerObj}"
converter="playerConverter" valueChangeListener="#{playerBean.selectedVCLAction}">
converter="playerConverter" valueChangeListener="#{playerBean.selectedVCLAction}">
<f:selectItems
value="#{playerBean.playersList}" var="t"
itemLabel="#{t.label}" itemValue="#{t}"/>
<f:ajax
execute="@this" render="@form"/>
</h:selectOneMenu>
</h:form>
The selectedVCLAction() is listed below:
private static final Logger LOG =
Logger.getLogger(PlayerBean.class.getName());
public void selectedVCLAction(ValueChangeEvent
event) {
LOG.log(Level.INFO, "selectedVCLAction()
called, {0}", ((Player)event.getNewValue()));
}
Niciun comentariu :
Trimiteți un comentariu