The <h:selectManyListbox> renders an HTML "select multiple" 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 selectManyListbox examples</title>
</h:head>
<h:body>
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}">
<f:selectItem
itemValue="Rafael Nadal" itemLabel="Rafa" />
<f:selectItem
itemValue="Roger Federer" itemLabel="Roger F" />
<f:selectItem
itemValue="Novak Djokovic" itemLabel="Nole" />
</h:selectManyListbox>
<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 selectManyListbox examples</title>
</h:head>
<h:body>
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}">
<f:selectItems
value="#{playerBean.playersMap}"/>
</h:selectManyListbox>
<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 selectManyListbox examples</title>
</h:head>
<h:body>
<h:form>
<h:selectManyListbox value="#{playerBean.selectedPlayers}">
<f:selectItems
value="#{playerBean.playersArray}" var="t"
itemLabel="#{t.label}" itemValue="#{t.value}"/>
</h:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
</h:body>
</html>
The <h:selectManyListbox>
will be rendered in HTML as:
<select
name="j_idt6:j_idt7" multiple="multiple"
size="3">
<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 String[] selectedPlayers;
private static final Map<String, Object>
playersMap;
private static final Player[] playersArray;
static {
playersMap = new LinkedHashMap<>();
playersMap.put("Rafa", "Rafael
Nadal"); //label, value
playersMap.put("Roger F",
"Roger Federer");
playersMap.put("Nole", "Novak
Djokovic");
}
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");
}
public Map<String, Object>
getPlayersMap() {
return
playersMap;
}
public Player[] getPlayersArray() {
return
playersArray;
}
public String[] getSelectedPlayers() {
return
selectedPlayers;
}
public void setSelectedPlayers(String[]
selectedPlayers) {
this.selectedPlayers = selectedPlayers;
}
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 players:
#{playerBean.selectedPlayers[0]}
#{playerBean.selectedPlayers[1]}
#{playerBean.selectedPlayers[2]}
</h:body>
</html>
Data flow in
image:
More examples:
Add xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" for pass-through attributes
Adjust the number of visible items via the 'size' attribute
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}" size="2">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
</h:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Styling messages with inner CSS
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}" 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:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Styling messages with CSS class
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}" styleClass="selectmanylistbox-css">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
</h:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Preselect some items via the bean's (post) constructor
<h:form>
<h:selectManyListbox value="#{playerBean.selectedPlayers}">
<f:selectItem itemLabel="Select a player ..."/>
<f:selectItems value="#{playerBean.playersArray}" var="t" itemLabel="#{t.label}" itemValue="#{t.value}"/>
</h:selectManyListbox>
<h:commandButton value="Select" action="#{playerBean.selectedAction()}"/>
</h:form>
And place initialization in the bean constructor:
selectedPlayers = new String[]{"Rafael Nadal","Novak Djokovic"};
@Named
@SessionScoped
public class PlayerBean implements Serializable {
...
...
public PlayerBean() {
}
}
...
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 SelectedPlayersBean selectedPlayersBean;
...
@Inject
private SelectedPlayersBean selectedPlayersBean;
...
@PostConstruct
public void init() {
selectedPlayers = selectedPlayersBean.getPlayers(); // must return String[] with valid items
}
}
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.
...
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.
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}">
<f:selectItem itemLabel="Select a player ..."
noSelectionOption="true"/>
<f:selectItems
value="#{playerBean.playersArray}" var="t"
itemLabel="#{t.label}" itemValue="#{t.value}"/>
</h:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
Use a built-in converter
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedRanks}" converter="javax.faces.Integer">
<f:selectItems value="#{playerBean.playersRanks}"/>
</h:selectManyListbox>
<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 selectedRanks
represents the user selections:
private
ArrayList<Integer> selectedRanks;
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
ArrayList<Integer> getSelectedRanks() {
return selectedRanks;
}
public void
setSelectedRanks(ArrayList<Integer> selectedRanks) {
this.selectedRanks = selectedRanks;
}
So, the user
may select the ranks and submit them without issues/errors. Even if no error occurred,
we can notice a "strange" behavior if we try to run the following
snippet of code:
<ui:repeat
value="#{playerBean.selectedRanks}" var="i">
#{i}: #{i.getClass()}
</ui:repeat>
The output
reveals that the selected ranks are strings, not integers as we expected to
see:
1: class
java.lang.String
3: 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:
1: class
java.lang.Integer
3: class
java.lang.Integer
Read further
explanations here.
Use a custom converter
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayersList}" converter="playerConverter">
<f:selectItems value="#{playerBean.playersList}"
var="t" itemLabel="#{t.label}"
itemValue="#{t}"/>
</h:selectManyListbox>
<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
ArrayList<Player> selectedPlayersList;
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
ArrayList<Player> getSelectedPlayersList() {
return selectedPlayersList;
}
public void
setSelectedPlayersList(ArrayList<Player> selectedPlayersList) {
this.selectedPlayersList =
selectedPlayersList;
}
Remember
from the above use case that the generic type of List<> is lost during
runtime. Since the selected items are treated as strings instead of Player
instances, the below code will cause an error because #{i.label} cannot be
evaluated:
<ui:repeat
value="#{playerBean.selectedPlayersList}" var="i">
#{i.label}: #{i.getClass()}
</ui:repeat>
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:selectManyListbox
value="#{playerBean.selectedPlayersList}" converter="omnifaces.SelectItemsConverter">
<f:selectItems
value="#{playerBean.playersList}" var="t"
itemLabel="#{t.label}" itemValue="#{t}"/>
</h:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
<h:form>
<h:selectManyListbox value="#{playerBean.selectedPlayersList}"
converter="omnifaces.SelectItemsIndexConverter">
<f:selectItems
value="#{playerBean.playersList}" var="t"
itemLabel="#{t.label}" itemValue="#{t}"/>
</h:selectManyListbox>
<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:selectManyListbox
value="#{playerBean.selectedPlayers}" 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:selectManyListbox>
<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:selectManyListbox
value="#{playerBean.selectedPlayers}" 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:selectManyListbox>
<h:commandButton value="Select"
action="#{playerBean.selectedAction()}"/>
</h:form>
AJAXify <h:selectManyListbox> (select a single item)
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayers}">
<f:selectItem itemValue="Rafael
Nadal" itemLabel="Rafa" />
<f:selectItem itemValue="Roger
Federer" itemLabel="Roger F" />
<f:selectItem itemValue="Novak
Djokovic" itemLabel="Nole" />
<f:ajax execute="@this"
render="@form"/>
</h:selectManyListbox>
#{playerBean.selectedPlayers[0]}
</h:form>
Use 'valueChangeListener' attribute with <h:selectManyListbox>
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayersList}" 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:selectManyListbox>
</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)((ArrayList)event.getNewValue()).get(0)).getLabel());
}
AJAXify <h:selectManyListbox> (select a single item) with 'valueChangeListener' attribute
<h:form>
<h:selectManyListbox
value="#{playerBean.selectedPlayersList}"
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:selectManyListbox>
</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)((ArrayList)event.getNewValue()).get(0)).getLabel());
}
Complete source code on GitHub.
See also Mkyong.com.
More resources on Constantin Alin, ZEEF page.
SelectManyListbox in JSF Extension on JSF ShowCase ZEEF page.
More resources on Constantin Alin, ZEEF page.
SelectManyListbox in JSF Extension on JSF ShowCase ZEEF page.
Niciun comentariu :
Trimiteți un comentariu