how to use value change listener - events

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?

Related

Reloading a <p:dataScroller> on a form update

Random Thought: I am hating this "lone wolf" behavior they coded into the dataScroller....
I am trying to implement a filter based on the user choice on a <p:selectOneMenu> that would reload the contents shown in a <p:dataScroller> from the ManagedBean based on the choice.
MB (EnglishNumberToWords) (random strings)
import java.util.*;
import se.answers.EnglishNumberToWords;
import java.security.SecureRandom;
#ManagedBean
#ViewScoped
public class bean {
private List<String> itens;
private Integer choice = 1; //initialize;
private LazyDataModel<String> model;
// getter setter
#PostConstruct
public void postConstruct() {
int count = loadStringsFromElsewhere();
model = new LazyModelImplmentation(this);
model.setRowCount(count);
}
public Map<String, Integer> mapChoices() {
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
for(int ii=0;ii<5;ii++) {
map.put(ii, convertLessThanOneThousand(ii));
}
}
public List<String> getChunk(int first, int pageSize) {
SecureRandom random = new SecureRandom();
int listSize = itens.size();
int added = 0;
int end = int+pageSize;
while(end > itens.size(){
added++; //the real code here is different, I will just randomize.
int criteria = (random.nextInt(5) + 1);
if(criteria == choice) { // filters out Strings.
String ss = criteria + BigInteger(130, random).toString(32)
itens.add(ss);
}
}
return itens.subList(Math.min(first, itens.size()), Math.min(end, itens.size()));
}
/**
* Get the dataScroller itens from elsewhere, NOT a database.<p>
* here we will use only randons.
*/
private int loadStringsFromElsewhere() {
SecureRandom random = new SecureRandom();
if(itens == null) {
itens = new ArrayList<String>();
}
for(int ii=0;ii< (random.nextInt(50) + 100); ii++) {
int criteria = (random.nextInt(5) + 1);
String ss = criteria + BigInteger(130, random).toString(32);
itens.add(ss);
}
}
}
LazyModelImpl
import java.util.List;
import java.util.Map;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;
public class LazyModelImplmentation extends LazyDataModel<String> {
private static final long serialVersionUID = 1L;
private Bean bean;
public LazyModelImplmentation(Bean bean) {
this.bean = bean;
}
#Override
public List<String> load(int first, int pageSize, String sortField,
SortOrder sortOrder, Map<String, Object> filters) {
return bean.getChunk(first, pageSize);
}
}
JSF
<h:form prependId="false">
<p:selectOneMenu value="#{bean.choice}">
<f:selectItems value="#{bean.mapChoices()}" />
<p:ajax process="#form" update="#form" />
</p:selectOneMenu>
<p:dataScroller id="da_scroller" var="item"
value="#{bean.model}" rowIndexVar="index" chunkSize="10" lazy="true">
<!-- SHOW THE DATA IN THE item -->
<h:outputText value="#{index}: #{item.toString()}" />
<hr />
</p:dataScroller>
</h:form>
But the dataScroller just ignores the form update and keeps showing the same data. Only the new data loaded via the lazy model is updated, mixed with the old data.
How can I clean up the dataScroller on the form update so it displays only the new data (bonus points if it goes back to the first chunk).
Using Primefaces 5.0 on Tomcat 7 and jsf2.2 (but the jsf is on the tagging).
After fiddling around with the source for the <p:dataScroller> I came up with no solution. There is no documented way to change what was already appended, and the component just appends more stuff.
So I had to hack my own solution:
Lie to the <p:dataScroller>:
The component does not work properly if you do not setRowCount() on the lazyModel. It only fetches two chunks and then stop.
Changing the rowCount on the fly also does not have the intended effect. The component keeps its own internal count. [3]
Also as of Primefaces 5.0, setting rowCount to Integer.MAX_VALUE causes the dataScroller to halt (client-side) on the fetch of the second chunk. I suspect some Shlemiel the painter [1] [2] somewhere.
So on the init of the LazyDataModel, set a rowCount large enough (but not too large). I set it to 100,000.
Cheat: I have control of what is going to the dataScroller because I build the chunk on the #ManagedBean, so If I want to reset the list and start serving from the beginning, I can. I will leave the exact implementation of the getChunk() method (see the listing on the question, above) to the reader, but just keep your own count instead of relying on the params of LazyDataModel<T>.load().
Steal: clean the already loaded entries on the AJAX call of the <p:selectOneMenu> (bind to the onstart, because you can't be sure to have a window to do it before the dataScroller updates itself:
<p:selectOneMenu value="#{bean.choice}">
<f:selectItems value="#{bean.mapChoices()}" />
<p:ajax process="#form" update="#form" onstart="cleanScroller()" />
</p:selectOneMenu>
function cleanScroller() {
$('li.ui-datascroller-item').remove();
}
There is an easy way:
Just put the datascroller inside a panel, then:
change the rendered atribute to false in the datascroller
update the panel
reload the datascroller list with new data
change the rendered atribute to true again in the datascroller
update the panel
= )

Multi field validation, obtaining value of another component results in NullPointerException

I get javax.faces.FacesException: java.lang.NullPointerException when I type something in the zip code and hit submit with country set to default null value. If I select the country and then type something everything works. I tried SubmittedValue, but it is working the opposite way - with null is working and after this is giving null exception.
#FacesValidator("zipV")
public class ZipValidator implements Validator {
LocaleBean Bean = new LocaleBean();
String language;
private static final String ZIP_PATTERN_BG = "\\d{4}";
private static final String ZIP_PATTERN_US = "\\d{5}";
private static final String ZIP_PATTERN_DEFAULT = "[A-Za-z0-9]*";
private String zip_pattern;
private Pattern pattern;
private Matcher matcher;
private String country;
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
language = Bean.getLanguage();
UIInput Input = (UIInput) component.getAttributes().get("country");
country = Input.getValue().toString();
String zip = (String) value;
if (country == null || country.isEmpty()) {
return;
}
switch (country) {
case "BGR":
zip_pattern = ZIP_PATTERN_BG;
break;
case "USA":
zip_pattern = ZIP_PATTERN_US;
break;
default:
zip_pattern = ZIP_PATTERN_DEFAULT;
break;
}
pattern = Pattern.compile(zip_pattern);
matcher = pattern.matcher(value.toString());
if (!matcher.matches()) {
switch (language) {
case "en": {
FacesMessage msg = new FacesMessage("Invalid zip.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
case "bg": {
FacesMessage msg = new FacesMessage("Невалиден пощенски код.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
}
}
}
}
Here's the view:
<h:selectOneMenu id="country" value="#{account.country}" required="true" requiredMessage="#{msg['register.required']}" binding="#{country}">
<f:selectItem itemValue="#{null}" itemLabel="#{msg['register.countryQ']}"/>
<f:selectItems value="#{account.countries}"/>
<f:ajax listener="#{account.loadStates()}" render="state"/>
</h:selectOneMenu>
<h:inputText id="zipcode" required="true" requiredMessage="#{msg['register.required']}" value="#{account.zipcode}">
<f:validator validatorId="zipV"/>
<f:attribute name="country" value="#{country}"/>
</h:inputText>
Here,
country = Input.getValue().toString();
you should not be using toString() at all. You should be casting it:
country = (String) Input.getValue();
Otherwise it will throw NullPointerException if the getValue() returned null. As its javadoc clearly says, a NullPointerException will be thrown among others when you attempt to call an instance method on null (like as you did with toString()).
Please note that this problem is technically unrelated to JSF. It's just basic Java. The java.lang package of the exception is a very good hint in this. If you got an exception of javax.faces (or javax.el) package, then we can talk about a true JSF (or EL) problem.
See also:
jsf validate two fields in one time
JSF doesn't support cross-field validation, is there a workaround?
Unrelated to the concrete problem, I'd really respect Java naming conventions. Variable names start with lowercase. Use input instead of Input. Also, your manual control of localization is strange. What if you ever need to support 10 languages? Do you expand the switches over all place? Make use of JSF builtin localization facilities with <resource-bundle> and ResourceBundle#getBundle().

JSF/AJAX Dynamic Menu - 404 issue

I am trying to make a dynamic menu such that when something is selected in the first selector, the second one is populated from the database based on the selection in the first one. Here is the .xhtml:
<f:view>
<h:form>
<h:selectOneMenu id="seasonSelector" value ="#{selector_bean.season}">
<f:ajax event="valueChange" listener="#{selector_bean.genEvents}"
execute="seasonSelector" render="eventSelector" />
<f:selectItems value ="#{selector_bean.seasons}" var ="s"
itemLabel="#{s.getRange()}"
itemValue="#{s}"></f:selectItems>
</h:selectOneMenu>
<h:selectOneMenu id="eventSelector">
<f:selectItems value ="#{selector_bean.events}" var ="e"
itemLabel="#{e.event_Name}"
itemValue="#{e}"></f:selectItems>
</h:selectOneMenu>
</h:form>
</f:view>
Here is the bean:
#ManagedBean(name = "selector_bean")
#Stateless
public class selector_bean implements Serializable{
#EJB
SeasonFacade sf;
#EJB
EventFacade ef;
#EJB
WrestlerFacade wf;
private Season season;
private Event event;
private List<Event> events;
private Match match;
private Wrestler wrestler;
public List<Season> getSeasons(){
return sf.findAll();
}
public void genEvents(AjaxBehaviorEvent event){
events = (ef.findBySeason(season));
}
// setters and getters after this
When I change the value of the first selectOneMenu, a popup box appears with this message:
httpError: There was an error communicating with the server, status: 404
I am new to both JSF and AJAX so feel free to tear me apart if I am doing it wrong. Thanks for any help!
Your #ManagedBean is behaving as an EJB with the #Stateless annotation. Remove it and instead set the scope of your bean to #ViewScoped:
#ManagedBean(name = "selectorBean")
#ViewScoped
public class SelectorBean implements Serializable{
//your implementation...
}
Also, make sure to follow the JavaBean naming conventions. I've changed the name of your class to start with capital letter.

How to make three JSF h:selectOneMenus that depend on each other

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

f:ajax and inputs inside ui:repeat - why aren't getters called?

I'm having trouble understanding why my getters aren't being called when I expect them inside a ui:repeat. I'm using Glassfish 3.1.1 / Mojarra 2.1.3.
The code below will render a table like
Alice [empty input] [empty output] [link: update value] [link: cancel]
Bob [empty input] [empty output] [link: update value] [link: cancel]
If I click "update value" on the Alice row, then "update value" on the "Bob" row, I end up with this:
Alice [Alice] Alice
Bob [Alice] Bob
I don't understand why the output for the "Bob" row is picking up "Alice" instead. It's like the getter isn't being called during the render-response phase, and instead the old value from the managed bean is stuck to the UIInput in the update-model-values phase.
What's weird is that if I hit "update value" on the Alice row, then "cancel", then "update value" on the Bob row, I get the expected result.
Also, if I add "#form" to the render=... on the "update value" link, I will see the right values (although they will be duplicated on each row). I don't like this as much, primarily because I don't want to update the whole table to process a single row.
What could be causing this? What am I missing about the JSF lifecycle?
Also - the same pattern works just fine outside of a ui:repeat. In that case, the h:inputText seems to always refresh with the right value from the managed bean, calling the getter in the "render response" phase as I expect.
This was originally using PrimeFaces p:commandLink but I get exactly the same behavior with standard JSF h:commandLink and f:ajax.
Also I'm aware of PrimeFaces row editor and that would possibly be a better solution to the general overall problem - I still want to understand why this doesn't work though.
Thanks!
The relevant XHTML is as follows
<h:form id="testForm">
<table style="width:400px; ">
<ui:repeat value="#{testBean.customers}" var="customer" varStatus="status">
<tr>
<td><h:outputText id="output" value="#{customer.name}"/></td>
<td><h:inputText id="input" value="#{testBean.customerEdit.name}"/></td>
<td><h:outputText id="otherOutput" value="#{testBean.customerEdit.name}"/></td>
<td>
<h:commandLink actionListener="#{testBean.edit(status.index)}">
<f:ajax execute="#this" render="input otherOutput"/>
Update value
</h:commandLink>
<h:commandLink actionListener="#{testBean.cancel}">
<f:ajax execute="#this" render="input otherOutput"/>
Cancel
</h:commandLink>
</td>
</tr>
</ui:repeat>
</table>
</h:form>
The "testBean" managed bean is view-scoped:
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ViewScoped
#ManagedBean
public class TestBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public static class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
System.out.println("returning name: " + name);
return name;
}
public void setName(String name) {
this.name = name;
}
}
private List<Customer> customers;
private Customer customerEdit = new Customer(null);
#PostConstruct
public void init() {
customers = Arrays.asList(new Customer("Alice"),
new Customer("Bob"), new Customer("Carol"), new Customer("David"), new Customer("Eve"));
}
public Customer getCustomerEdit() {
return customerEdit;
}
public void setCustomerEdit(Customer customerEdit) {
this.customerEdit = customerEdit;
}
public void edit(int index) {
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
public void save(int index) {
System.out.println("Called save with index: " + index + " new name = " + customerEdit.getName());
customers.set(index, customerEdit);
customerEdit = null;
}
public void cancel() {
System.out.println("Called cancel");
customerEdit = null;
}
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
}
Your problem lies in this row:
<h:commandLink actionListener="#{testBean.edit(status.index)}">
You can't send arguments to actionlisteners this way, that's not how it works. You need to change that row to something like:
<h:commandLink actionListener="#{testBean.edit}" customerIndex="#{status.index}>
And then change the edit method to something like this.
public void edit(ActionEvent ae) {
int index = ae.getComponent().getAttributes().get("customerIndex");
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
Also I'm not sure how your "save" method relates to anything else, but that's probably just because you skipped some irrelevant code.
EDIT: You can send arguments this way if it's a javascript method, but not to managed beans or anything else inside the #{} tags.

Resources