My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

.

.

.

.

.

.

.

.


[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

duminică, 30 august 2015

[JSF Page Author Beginner's Guide] JSF <selectManyListbox> / HTML5 <select> [multiple]

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:

@Named
@SessionScoped
public class PlayerBean implements Serializable {
 ...
 public PlayerBean() {
  selectedPlayers = new String[]{"Rafael Nadal","Novak Djokovic"};
 }
 ...
}

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; 
 ...
 @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.

Add a no selection option

<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}">               
  <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}">               
  <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.

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

Visitors Starting 4 September 2015

Locations of Site Visitors