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
= )
Related
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
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'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.
This question already has answers here:
JSF doesn't support cross-field validation, is there a workaround?
(2 answers)
Closed 5 years ago.
can I validate two interdependent fields in with one validator?
<h:form>
<h:inputText value="#{logRegBean.person.name}" >
<f:validator validatorId="loginCorrectValidator" />
</h:inputText>
<h:inputSecret value="#{logRegBean.person.password}" />
<h:commandButton action="#{logRegBean.login}" />
</h:form>
I want to search for the user in the DB and if there is the user, I'll test if the passwords(in db and inputted) match. But how can I access even the password field in one validator? I tried to evaluate the value int the other field via createValueExpression(), but it looks like I can't access the value in that time since I always get empty strings.
Best what you can do is to grab the other UIInput component by UIViewRoot#findComponent() inside the validate() method and then determine the submitted value by either UIInput#getSubmittedValue() (when it occurs after the currently validated component in the component tree) or UIInput#getValue() (when it occurs before the current component and thus is already validated).
E.g.
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
UIInput otherInput = (UIInput) context.getViewRoot().findComponent("clientId");
String otherValue = (String) otherInput.getSubmittedValue();
// ...
}
See also:
JSF doesn't support cross-field validation, is there a workaround?
The validation mechanism in JSF was designed to validate a single component.
However, in practice, you often need to ensure that related components have reasonable values before letting the values propagate into the model.
For example, it is not a good idea to ask users to enter a date into a single textfield.
Instead, you would use three different textfields, for the day, month, and year.
If the user enters an illegal date, such as February 30, you would like to show a validation error and prevent the illegal data from entering the model.
The trick is to attach the validator to the last of the components. By the time its validator is called, the preceding components passed validation and had their local values set. The last component has passed conversion, and the converted value is passed as the Object parameter of the validation method.
Of course, you need to have access to the other components. You can easily achieve that access by using a backing bean that contains all components of the current form. Simply attach the validation method to the backing bean:
public class BackingBean {
private int day;
private int month;
private int year;
private UIInput dayInput;
private UIInput monthInput;
private UIInput yearInput;
// PROPERTY: day
public int getDay() { return day; }
public void setDay(int newValue) { day = newValue; }
// PROPERTY: month
public int getMonth() { return month; }
public void setMonth(int newValue) { month = newValue; }
// PROPERTY: year
public int getYear() { return year; }
public void setYear(int newValue) { year = newValue; }
// PROPERTY: dayInput
public UIInput getDayInput() { return dayInput; }
public void setDayInput(UIInput newValue) { dayInput = newValue; }
// PROPERTY: monthInput
public UIInput getMonthInput() { return monthInput; }
public void setMonthInput(UIInput newValue) { monthInput = newValue; }
// PROPERTY: yearInput
public UIInput getYearInput() { return yearInput; }
public void setYearInput(UIInput newValue) { yearInput = newValue; }
public void validateDate(FacesContext context, UIComponent component, Object value) {
int d = ((Integer) dayInput.getLocalValue()).intValue();
int m = ((Integer) monthInput.getLocalValue()).intValue();
int y = ((Integer) value).intValue();
if (!isValidDate(d, m, y)) {
throw new ValidatorException(new FacesMessage("Invalid Date"));
}
}
private static boolean isValidDate(int d, int m, int y) {
//DO YOUR VALIDATION HERE
}
}
Here is your JSP
<html>
<%# taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%# taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head></head>
<body>
<h:form>
<h:panelGrid columns="3">
<h:inputText value="#{bb.day}" binding="#{bb.dayInput}" size="2" required="true"/>
<h:inputText value="#{bb.month}" binding="#{bb.monthInput}" size="2" required="true"/>
<h:inputText value="#{bb.year}" binding="#{bb.yearInput}" size="4" required="true" validator="#{bb.validateDate}"/>
<h:message for="year" styleClass="errorMessage"/>
</h:panelGrid>
<h:commandButton value="Submit" action="submit"/>
</h:form>
</body>
</f:view>
</html>
Reference:
Core JavaServerâ„¢ Faces
By DAVID GEARY, CAY HORSTMANN
Publisher : Addison Wesley
Pub Date : June 15, 2004
ISBN : 0-13-146305-5
I think SeamFaces' s:validateForm feature may be just what you need. (Seam Faces is a very useful library that brings some nifty CDI-based features to JSF.)
I am currently trying to dynamically add a new component to the JSF component tree during an ajax request.
In fact I add a child to the UIViewRoot component in my AjaxBehaviorListener which is fired on server side during the ajax request process.
The issue is that the new component is not rendered. It seems that this component is not taken into account in the render response phase.
Could you help me on this issue ?
Regards,
Guillaume
This solution works in case of you know before the ajax request the component to add.
But if you are not able to know which component to add, it does not work.
I maybe found a solution :
My solution is to implement my custom PartialViewContext and use the method startInsertAfter or startInsertBefore of the PartialResponseWriter.
It is working, but you have to put the component added as transient. (uiComponent.setTransient(Boolean.TRUE);)
Regards,
Guillaume
This works for me:
Bean holding binding to UIComponent under which you want to add other UIComponents dynamically should be request scoped otherwise it can throw some nasty exceptions (don't ask me why):
#ManagedBean
#RequestScoped
public class AddressEditorBean {
// session bean reference for holding id number line and model
#ManagedProperty(value = "#{addressValueBean}")
private AddressValueBean address;
public String addOutputText() {
HtmlOutputText text = new HtmlOutputText();
int c = address.getC();
text.setValue("new text! " + c);
text.setId("id" + c++);
address.setC(c); // hold id number line in sessionbean
address.getComps().add(text); // hold new uicomponent in session bean to be able to rebuild it
panel.getChildren().clear(); // need to clear children and add them all again,
panel.getChildren().addAll(address.getComps()); // otherwise there are problems with duplicate children (bug?)
return "success";
}
public HtmlPanelGroup getPanel() {
return panel;
}
public void setPanel(HtmlPanelGroup pane) {
if (panel == null) {
this.panel = pane;
if (panel != null) {
panel.getChildren().addAll(address.getComps());
}
} else {
this.panel = pane;
}
}
}
code snippet from page. I dynnamically add components to <h:panelGroup>
<h:form>
<h:panelGroup id="section" binding="#{addressEditorBean.panel}">
</h:panelGroup>
<h:commandButton value="add new text" action="#{addressEditorBean.addOutputText}">
<f:ajax execute="#this" render="section" event="action"/>
</h:commandButton>
</h:form>
In Session bean I hold actual dynamic model so that I can rebuild it after page reload:
#ManagedBean
#SessionScoped
public class AddressValueBean extends ValueBean<Address> {
private int c = 0;
private List<UIComponent> comps = new ArrayList<UIComponent>();
public AddressValueBean() {
setValue(new Address());
}
public int getC() {
return c;
}
public void setC(int cn) {
this.c = cn;
}
public List<UIComponent> getComps() {
return comps;
}
public void setComps(List<UIComponent> comps) {
this.comps = comps;
}
}
Instead of trying to add the component dynamically during the ajax request, try defining the component upfront, and setting it's rendered tag to false. The contents of the component can then be populated with the ajax request, and the rendered attribute flipped to display the attribute.