I hope someone can help me. This is a "Category-Type-Item" situation where the user has to select from three different drop-down lists. In JSF I made it with three h:selectoneMenu. The first two h:selectOneMenu work OK. I read a solution here, about a "malformedXML" error when I tried the first time. That's the reason each selectOneMenu is surrounded by a h:panelGorup. I implemented it and work partially and only after reload (F5) the page, but this is not an acceptable solution.
Here is the code that I'm implementing:
<p:panel rendered="#{temporadaControllerExt.noHayGrupos}"
style="width: 650px">
<h:panelGrid columns="3">
<h:panelGroup id="fases">
<h:outputLabel value="Fase: "/>
<h:selectOneMenu value="#{temporadaControllerExt.faseSelectedFinal}">
<f:selectItems value="#{temporadaControllerExt.fases}"/>
<f:ajax render="grupos"/>
</h:selectOneMenu>
</h:panelGroup>
<h:panelGroup id="grupos">
<h:outputLabel value="Grupo: "/>
<h:selectOneMenu value="#{temporadaControllerExt.grupoSelected}"
disabled="#{temporadaControllerExt.grupoListDisable}">
<f:selectItems value="#{temporadaControllerExt.gruposDefase}"/>
<f:ajax render="jornadas"/>
</h:selectOneMenu>
</h:panelGroup>
<h:panelGroup id="jornadas">
<h:outputLabel value="Jornada: "/>
<h:selectOneMenu value="#{temporadaControllerExt.jornadaSelected}"
disabled="#{temporadaControllerExt.jornadaListDiseable}">
<f:selectItems value="#{temporadaControllerExt.jornadasEnGrupo}"/>
</h:selectOneMenu>
</h:panelGroup>
Of course is not complete, for space reasons. And here is part of the bean controller that I'm using:
#ManagedBean(name = "temporadaControllerExt")
#SessionScoped
public class TemporadaControllerExt extends TemporadaController {
private Fase fase;
private Grupo grupo;
private Jornada jornada;
private String faseSelectedFinal;
private String grupoSelected;
private String jornadaSelected;
private boolean grupoListDiseable = true;
private boolean jornadaListDiseable = true;
// a lot more declarations, and methods incluiding getters & setters not important for the answer...
//Entities Cache
private Map<String, Fase> mapFases = new HashMap<>();
private Map<String, Grupo> mapGrupos = new HashMap<>();
// use to enable the second selectOneMenu
public void setFaseSelectedFinal(String faseSelectedFinal) {
this.faseSelectedFinal = faseSelectedFinal;
this.grupoListDiseable = false;
}
public void setGrupoSelected(String grupoSelected) {
this.grupoSelected = grupoSelected;
this.jornadaListDiseable = false;
}
// methods used to populate the selecOneMenu's
public List<SelectItem> getFases(){
List<SelectItem> fasesList = new ArrayList<>();
fasesList.add(new SelectItem("-- Seleccione Fase --"));
for(Map.Entry<String, Fase> item : mapFases.entrySet()){
fasesList.add(new SelectItem(item.getKey()));
}
return fasesList;
}
public List<SelectItem> getGruposDefase(){
List<SelectItem> grupoList = new ArrayList<>();
grupoList.add(new SelectItem("-- Seleccione Grupo --"));
Fase faseSel = mapFases.get(faseSelectedFinal);
List<Grupo> grupoInt = faseSel.getGrupoList();
for(Grupo grp : grupoInt){
grupoList.add(new SelectItem(grp.getNombre()));
}
return grupoList;
}
public List<SelectItem> getJornadasEnGrupo(){
List<SelectItem> jornadaList = new ArrayList<>();
jornadaList.add(new SelectItem("-- Seleccione Jornada --"));
Grupo grupoSel = mapGrupos.get(grupoSelected);
for(Jornada jor : grupoSel.getJornadaList()){
jornadaList.add(new SelectItem(jor.getNumero().toString()));
}
return jornadaList;
}
}
As you can appreciate I'm using primefaces (3.4), and this panel is rendered in accordance to the value of the "noHayGrupos" boolean variable. This works OK, my problem is the selectOneMenu chain.
After some investigation on my problem, I found a solution. I read a tutorial in this place, the section relative to "Integrated Ajax Support in JSF 2.0" by Marty Hall. The solution was quite simple:
Declare a boolean variable to determinate if a comboBox is selected or not, and ask if a previous combobox has a data valid selected, and that's it, worked every thing. Here the code that I use in the method getGruposDefase():
public List<SelectItem> getGruposDefase() {
List<SelectItem> grupoList = new ArrayList<>();
grupoList.add(new SelectItem("-- Seleccione Grupo --"));
if (!grupoListDiseable && (faseSelectedFinal != null)) {
Fase faseSel = mapFases.get(faseSelectedFinal);
List<Grupo> grupoInt = faseSel.getGrupoList();
for (Grupo grp : grupoInt) {
grupoList.add(new SelectItem(grp.getNombre()));
}
}
return grupoList;
}
The variable grupoListDisable is used to control access to the second comboBox, and faseSelectedFinal, say if you have selected a valid item in the first comboBox. This simple solution makes the code work smoothly. Thanks Marty Hall
Related
Please have a look at the following JSF page and at the definition of the managed bean class which it references.
When I run my app and load the page I get the results shown in the "Good Results" screen shot. Note that the page is displaying data and that the "Hello World" string printed by the bean's constructor appears in the console.
If I alter the bean's definition by commenting out the definition of the "junk" integer (near the top) then I get the results shown in the "Bad Results" screen shot. Now there is no data and, most tellingly, the "Hello World" string does not appear in the console. No errors are reported to the console. The page is being rendered but it appears as though the JSF engine has decided that it does not like the bean's definition and is therefore not going to use it (the constructor is not called).
I have tried mightily to produce a Minimal, Complete, and Verifiable example. I eliminated several forms from the JSF page (you can observe that much of the bean's code is no longer being referenced by the JSF page). I eliminated JPA from the picture by creating the DummyDataAccessService class. I tried to eliminate the use of one or more of my own custom classes (Order, Patient, Product, DataAccessService, and DummyDataAccessService) but I could not: almost any alteration of the bean's definition produces the same strange behavior that results from removing the definition of the "junk" member variable.
I created a custom logging.properties file which bumped the level to ALL. The logging produced by the Good vs. Bad cases were close to the same. The two "Logging Differences" screen shots below reveal the primary differences.
I do not know how to explore this further. I don't know enough to even conjecture what might be going on. Any clue or recommendation on a course of action will be greatly appreciated.
JSF Page
<!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:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<h:outputStylesheet library="default" name="css/style.css" />
<title>Managed Bean Problem</title>
</h:head>
<h:body>
<h3 class="title">Managed Bean Problem</h3>
<h:outputText value=" " />
<table align="center" width="600">
<tr><td><h:form>
<h:dataTable value="#{orderController.table}" var="order" styleClass="demo" columnClasses="columns, columns">
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Patient"></h:outputText>
</h:column>
</f:facet>
<h:outputText value="#{order.patient.fullName}"></h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Product"></h:outputText>
</h:column>
</f:facet>
<h:outputText value="#{order.product.name}"></h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Actions"></h:outputText>
</h:column>
</f:facet>
<h:panelGrid columns="1">
<h:commandLink value="delete" action="#{orderController.delete}">
<f:setPropertyActionListener target="#{orderController.target}" value="#{order}" />
</h:commandLink>
</h:panelGrid>
</h:column>
</h:dataTable>
</h:form></td></tr>
</table>
</h:body>
Order Controller Managed Bean
package com.rvaessen.dmescripts.controllers;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
import com.rvaessen.dmescripts.model.*;
#ManagedBean
public class OrderController {
private int junk; // Leave it in; Good. Comment it out; Bad.
private DataAccessService dataService = new DummyDataAccessService();
public OrderController() {
System.out.println("Hello, World");
}
#PostConstruct
public void init() {
initPatients();
initProducts();
initOrders();
}
// ********************************* The Orders Table ***************************************
private Order target;
private List<Order> table;
private void initOrders() { table = dataService.getOrders(); }
public List<Order> getTable() { return table; }
public void setTarget(Order order) { target = order; }
public String delete() {
dataService.removeOrder(target);
table.remove(target);
return null;
}
// ********************************* Add New Order ***************************************
// NOTE: The Add New Order methods are no longer referenced by the JSF page
private Order newOrder;
public String addNew() {
newOrder = new Order();
return null;
}
public String save() {
dataService.addOrder(newOrder, patient, product);
table.add(newOrder);
cancel();
return null;
}
public String cancel() {
newOrder = null;
return null;
}
public boolean getAddingNew() { return newOrder != null; }
/************************ The Patients Menu **********************************/
// NOTE: The Patients Menu methods are no longer referenced by the JSF page
private Patient patient;
private List<Patient> patients;
private void initPatients() {
patients = dataService.getPatients();
if (patients.size() > 0) patient = patients.get(0);
}
public List<SelectItem> getPatients() {
List<SelectItem> list = new ArrayList<SelectItem>();
patients.forEach(patient -> list.add(new SelectItem(patient.getId(), patient.getFullName())));
return list;
}
public Long getPatientId() {
return patient == null ? 0 : patient.getId();
}
public void setPatientId(Long id) {
patients.forEach(patient -> {
if (patient.getId() == id) {
this.patient = patient;
}
});
}
/************************ The Products Menu **********************************/
// NOTE: The Products Menu methods are no longer referenced by JSF page
private Product product;
private List<Product> products;
private void initProducts() {
products = dataService.getProducts();
if (products.size() > 0) product = products.get(0);
}
public List<SelectItem> getProducts() {
List<SelectItem> list = new ArrayList<SelectItem>();
if (patient != null) {
products.forEach(product -> {
if (product.getInsurance().equals(patient.getInsurance())) {
list.add(new SelectItem(product.getId(), product.getName()));
}
});
}
return list;
}
public Long getProductId() {
return product == null ? 0 : product.getId();
}
public void setProductId(Long id) {
products.forEach(product -> {
if (product.getId() == id) {
this.product = product;
}
});
}
}
Good Results
Bad Results
Log Differences 1
Log Differences 2
This issue was never solved. It was made to go away by recreating the Eclipse project from scratch; simultaneously upgrading to JSF 2.3 / CDI 1.2.
I am trying to make 4 dependent / cascading selection components. In this question, the selection component happens to be a <h:selectOneMenu>, but this is of course applicable on any other kind of selection component extending from UISelectOne/UISelectMany superclass, such as <h:selectManyCheckbox> or PrimeFaces <p:selectCheckboxMenu>, <p:selectManyMenu>, etc.
When the user chooses an item from the first menu, the second menu will show dependent data and when the user chooses item from the second one , the third one will show dependent data and so on.
The user will see items on the first menu only and the other ones will be blank. If he chooses an item on the first menu the second one will show data but the third and the fourth will remain blank, and so on. The user must eventually choose entries from all the 4 menus.
<h:selectOneMenu id="first" value="#{nodes.selectState}">
<f:selectItems value="#{nodes.stateList}"/>
<f:ajax render="second">
</h:selectOneMenu>
<h:selectOneMenu id="second" value="#{nodes.selectCity}">
<f:selectItems value="#{nodes.cityList}"/>
<f:ajax render="third">
</h:selectOneMenu>
<h:selectOneMenu id="third" value="#{nodes.selectRegion}">
<f:selectItems value="#{nodes.regionList}"/>
<f:ajax render="fourth">
</h:selectOneMenu>
<h:selectOneMenu id="fourth" value="#{nodes.selectStation}">
<f:selectItems value="#{nodes.stationList}"/>
</h:selectOneMenu>
Nodes bean
private String selectState; //+setters, getters
private String selectCity; //+setters, getters
private String selectRegion; //+setters, getters
private String selectStation; //+setters, getters
private List<SelectItem> stateList; //+setters, getters
private List<SelectItem> cityList; //+setters, getters
private List<SelectItem> regionList; //+setters, getters
private List<SelectItem> stationList; //+setters, getters
public getStateList(){
stateList= new ArrayList<SelectItem>();
stateList.add(new SelectItem("A"));
}
public getCityList(){
CityList= new ArrayList<SelectItem>();
if(selectState.equals("A")){
CityList.add(new SelectItem("B"));
}
}
public getRegionList(){
RegionList= new ArrayList<SelectItem>();
if(selectCity.equals("B")){
RegionList.add(new SelectItem("C"));
}
}
public getStationList(){
StationList= new ArrayList<SelectItem>();
if(selectRegion.equals("C")){
StationList.add(new SelectItem("D"));
}
}
It's only working for the first 2 menus. The other 2 menus get null values.
Put the bean in the view scope and get rid of any business logic in getter methods.
The bean must be placed in the view scope so that all previous selections and new available items are remembered for subsequent postbacks, otherwise things will fail when JSF needs to validate the selected item against the list of available items which was prepopulated in a previous selection, or when e.g. rendered attribute depends on a condition which was only set in a previous request.
The getter methods may not contain any business logic as they will also be invoked during among others the validations phase. You should use <f:ajax listener>/<p:ajax listener> to perform business logic based on a change. You should in the listener method also explicitly clear out selected values of child selection components. You can use <f:ajax render>/<p:ajax update> to update the contents of child selection components.
Thus, so:
<h:selectOneMenu id="state" value="#{nodes.selectedState}">
<f:selectItem itemValue="#{null}" itemLabel="-- select --" />
<f:selectItems value="#{nodes.availableStates}" />
<f:ajax listener="#{nodes.changeState}" render="city region station" />
</h:selectOneMenu>
<h:selectOneMenu id="city" value="#{nodes.selectedCity}">
<f:selectItem itemValue="#{null}" itemLabel="-- select --" />
<f:selectItems value="#{nodes.availableCities}" />
<f:ajax listener="#{nodes.changeCity}" render="region station" />
</h:selectOneMenu>
<h:selectOneMenu id="region" value="#{nodes.selectedRegion}">
<f:selectItem itemValue="#{null}" itemLabel="-- select --" />
<f:selectItems value="#{nodes.availableRegions}" />
<f:ajax listener="#{nodes.changeRegion}" render="station" />
</h:selectOneMenu>
<h:selectOneMenu id="station" value="#{nodes.selectedStation}">
<f:selectItem itemValue="#{null}" itemLabel="-- select --" />
<f:selectItems value="#{nodes.availableStations}" />
</h:selectOneMenu>
with
#Named
#ViewScoped
public class Nodes {
private String selectedState; // getter+setter
private String selectedCity; // getter+setter
private String selectedRegion; // getter+setter
private String selectedStation; // getter+setter
private List<SelectItem> availableStates; // getter (no setter necessary!)
private List<SelectItem> availableCities; // getter (no setter necessary!)
private List<SelectItem> availableRegions; // getter (no setter necessary!)
private List<SelectItem> availableStations; // getter (no setter necessary!)
#EJB
private SomeService someService;
#PostConstruct
public void init() {
availableStates = someService.listStates();
}
public void changeState(AjaxBehaviorEvent event) {
availableCities = someService.listCities(selectedState);
selectedCity = selectedRegion = selectedStation = null;
availableRegions = availableStations = null;
}
public void changeCity(AjaxBehaviorEvent event) {
availableRegions = someService.listRegions(selectedCity);
selectedRegion = selectedStation = null;
availableStations = null;
}
public void changeRegion(AjaxBehaviorEvent event) {
availableStations = someService.listStations(selectedRegion);
selectedStation = null;
}
// Generate necessary getters+setters here. You should not change them.
}
See also:
How to choose the right bean scope?
Best way to add a "nothing selected" option to a selectOneMenu in JSF
How to populate options of h:selectOneMenu from database?
When to use valueChangeListener or f:ajax listener?
Why JSF calls getters multiple times
Try this, it may help you
By using the --Select City-- , --Select Region--, --Select Station-- to avoid the null pointer Exception.
public getStateList(){
stateList= new ArrayList<SelectItem>();
stateList.add(new SelectItem("A"));
}
public getCityList(){
CityList= new ArrayList<SelectItem>();
if(selectState.equals("A"))
{
CityList.add(new SelectItem("B"));
}
else
{
CityList.add(new SelectItem("--Select City--"));
selectCity = "--Select City--";
}
public getRegionList(){
RegionList= new ArrayList<SelectItem>();
if(selectCity.equals("B"))
{
RegionList.add(new SelectItem("C"));
}
else
{
RegionList.add(new SelectItem("--Select Region--"));
selectRegion = "--Select Region--";
}
}
public getStationList(){
StationList= new ArrayList<SelectItem>();
if(selectRegion.equals("C"))
{
StationList.add(new SelectItem("D"));
}
else
{
StationList.add(new SelectItem("Select Station"));
selectStation = "--Select Station--";
}
}
There is a typo error in your code. For third menu you have given id name as "first" instead of "third". May be its because of that problem.
You are facing this issue because you have twice id="first".
Fix this and it should work.
I have a problem and i simplfied the code to show you.
<h:form enctype="multipart/form-data">
<h:panelGroup id="images">
<h:inputText id="auctionImage" value="#{testBean.nowy}"/>
<h:commandButton value="Add">
<f:ajax execute="auctionImage" render="images"/>
</h:commandButton>
<ui:repeat value="#{testBean.elements}" var="oneImage">
<h:outputText value="#{oneImage.title}" />
</ui:repeat>
</h:panelGroup>
</h:form>
This is my main Bean
#SessionScoped
#ManagedBean
public class TestBean {
private List<Element> elements;
private String nowy;
public String getNowy() {
return nowy;
}
public void setNowy(String nowy) {
Element el = new Element();
el.setTitle(nowy);
if(elements==null) elements = new ArrayList<>();
elements.add(el);
this.nowy = nowy;
}
public List<Element> getElements() {
return elements;
}
public void setElements(List<Element> elements) {
this.elements = elements;
}
}
This is the element class
#ManagedBean
public class Element {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
I want to populate the list everytime I click Add button and print it using ajax. The list is populated but ajax refresh the panelGroup only for the first time. To make it work again I have to refresh website. What i do wrong?
Ok i was managed to resolve the issue. Well, to be honest it wasn't me but jsf programmers. I found out that it happens when you use
<h:form enctype="multipart/form-data">
with ajax. This is the bug and it has been repaired some time ago. You just have to update your jsf version.
http://javaserverfaces.java.net/ - i got 2.2.3 version which is the newest for now.
After restarting glassfish and redeploying application everything works just fine.
I don't know how to deal with f:valueChangeListener , I want to select country and the capital appear so this is my code but it doen't work what is miss or what is the wrong?
Country:
<h:selectOneMenu value="#{event.country}" onchange="submit()">
<f:valueChangeListener type="org.jsf.model.ValueListener"/>
<f:selectItems value="#{event.countries}"/>
</h:selectOneMenu>
Capital: #{event.capital}
My Managed bean
public class EventsBean{
private String capital;
private String country;
String countryCapital;
private String [] countries = {"Select","Egypt","United States","Kuwait"};
public String[] getCountries() {
return countries;
}
// getters and setters
}
The class that implements ValueChangeListener
package org.jsf.model;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
public class ValueChangeClass implements ValueChangeListener {
String capital;
#Override
public void processValueChange(ValueChangeEvent event)throws AbortProcessingException {
if ("Egypt".equals(event.getNewValue()) capital = "Cairo";
else if ("Kuwait".equals(event.getNewValue())) capital = "Kuwait";
else if ("United States".equals(event.getNewValue())) capital = "Washington";
else capital = "";
new EventsBean().setCapital(capital);
}
}
It doesn't work !
Is this new EventsBean().setCapital(capital);right ?
Is this new EventsBean().setCapital(capital);right ?
No, it is not right. You're manually creating a brand new instance instead of using the one which is managed by JSF. Your instance would totally disappear once the method finishes and returns. You should instead be setting the capital in the instance which is managed by JSF. There are several ways to achieve this. If you really intend to use the ValueChangeListener this way (which is rather strange for this particular purpose by the way), then you need to fix it as follows:
FacesContext context = FacesContext.getCurrentInstance();
EventsBean eventsBean = context.getApplication().evaluateExpressionGet(context, "#{event}", EventsBean.class);
eventsBean.setCapital(capital);
Easier would be to do the job just in the EventsBean itself.
<h:selectOneMenu value="#{event.country}" valueChangeListener="#{event.changeCountry}" onchange="submit()">
<f:selectItems value="#{event.countries}"/>
</h:selectOneMenu>
Capital: #{event.capital}
private String country;
private String capital;
private Map<String, String> capitals;
#PostConstruct
public void init() {
capitals = new HashMap<>();
capitals.put("Egypt", "Cairo");
capitals.put("Kuwait", "Kuwait");
capitals.put("United States", "Washington D.C.");
}
public void changeCountry(ValueChangeEvent event) {
capital = capitals.get(event.getNewValue());
}
Or, since you're already using JSF 2.0, much better is to use <f:ajax>. It does the right job at the right moment. (Ab)using the valueChangeListener the way as in your original code is actually a leftover of the JSF 1.x era.
<h:selectOneMenu value="#{event.country}">
<f:selectItems value="#{event.countries}"/>
<f:ajax listener="#{event.changeCountry}" render="capital" />
</h:selectOneMenu>
Capital: <h:outputText id="capital" value="#{event.capital}" />
// ...
public void changeCountry() {
capital = capitals.get(country);
}
See also:
When to use valueChangeListener or f:ajax listener?
I have this form:
<h:form>
<h:outputText value="Tag:" />
<h:inputText value="#{entryRecorder.tag}">
<f:ajax render="category" />
</h:inputText>
<h:outputText value="Category:" />
<h:inputText value="#{entryRecorder.category}" id="category" />
</h:form>
What I'm trying to achieve: When you type in the "tag" field, the entryRecorder.tag field is updated with what was typed. By some logic upon this action the bean also updates its category field. This change should be reflected in the form.
Questions:
What scope shall I use for EntryRecorder? Request may not be satisfactory for multiple AJAX requests, while session will not work with multiple browser windows per one session.
How can I register my updateCategory() action in EntryRecorder so that it is triggered when the bean is updated?
Answering point 2:
<h:inputText styleClass="id_tag" value="#{entryRecorder.tag}"
valueChangeListener="#{entryRecorder.tagUpdated}">
<f:ajax render="category" event="blur" />
</h:inputText>
Bean:
#ManagedBean
#ViewScoped
public class EntryRecorder {
private String tag;
private String category;
#EJB
private ExpenseService expenseService;
public void tagUpdated(ValueChangeEvent e) {
String value = (String) e.getNewValue();
setCategory(expenseService.getCategory(value));
}
}
Number 1, anybody?
To point 1, I'll use Request since there is no need to use View and Session is, as you well pointed, completely unnecessary.
For point 2, since you are using <f:ajax/> I suggest making full use of it. Here is my proposal:
xhtml:
<h:form>
<h:outputText value="Tag:" />
<h:inputText value="#{entryRecorder.tag}">
<f:ajax render="category" event="valueChange"/>
</h:inputText>
<h:outputText value="Category:" />
<h:inputText value="#{entryRecorder.category}" id="category" />
</h:form>
Note the use of valueChange event instead of blur (not that blur doesn't work but I find valueChange more 'proper' for a value holder component).
bean:
#ManagedBean
#RequestScoped
public class EntryRecorder {
private String tag;
private String category;
public String getCategory() {
return category;
}
public String getTag() {
return tag;
}
public void setCategory(String category) {
this.category = category;
}
public void setTag(String tag) {
this.tag = tag;
tagUpdated();
}
private void tagUpdated() {
category = tag;
}
}
Unless you really want the tagUpdated method executed only when tag is updated through the view, my proposal looks more clear. You don't have to deal with the events (nor casting) and the tagUpdated method can be private hiding it's functionality from possible misuses.