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.
Related
I have a <h:selectManyCheckbox> that has a required-validation on. If I submit the form, I get a validation error when nothing is selected. So far, this ist expected. However, if I do an ajax update on the checkbox then, I get a ClassCastException. But only if empty values are treated as null.
So, I have the following setup. In the web.xml I set
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
Then I have an xhtml-page like this:
<h:form id="main">
<h:selectManyCheckbox id="value" value="#{testcb.selected}" required="true" requiredMessage="Select at least one entry">
<f:selectItems value="#{testcb.available}"/>
</h:selectManyCheckbox>
<div><h:message for="value" style="color:red;"/></div>
<h:outputLabel for="checkit" value="Enter some text: "/>
<h:inputText id="checkit" value="#{testcb.text}">
<f:ajax event="change" execute="#this" render=":main:value"/>
</h:inputText>
<div><h:commandButton type="submit" value="Submit" action="#{testcb.action}"/></div>
</h:form>
And this backing bean:
#Named("testcb")
#SessionScoped
public class TestCBBean implements Serializable {
private final Set<TestValue> available = EnumSet.allOf(TestValue.class);
private final Set<TestValue> selected = EnumSet.noneOf(TestValue.class);
private String text;
public void action() {}
public Set<TestValue> getAvailable() { return available; }
public void setAvailable(Set<TestValue> available) {
this.available.clear();
this.available.addAll(available);
}
public Set<TestValue> getSelected() { return selected; }
public void setSelected(Set<TestValue> selected) {
this.selected.clear();
this.selected.addAll(selected);
}
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
And this enum:
public enum TestValue { ONE, TWO, THREE }
I am running this in Wildfly 26.0.1-Final (JavaEE 8). But this also happens in older versions (like Wildfly 15). What I am doing:
enter some text and leave the box: an ajax update runs setting the value successfully in the model
I press submit: the validation error for the empty checkboxes pops up
I modify the text in the input and leave the box: the ajax update results in the following Exception:
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.MenuRenderer.getSubmittedSelectedValues(MenuRenderer.java:508)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:89)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1655)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:628)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:159)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIForm.visitTree(UIForm.java:355)
On the ajax update the checkboxes are not submitted. But they seem to contain an empty string as submitted value from the validation step before.
When setting the context parameter to false this works. But I want to keep it on true. Any ideas how I could work around this problem?
Reproduced. This is indeed a bug in Mojarra.
It boils down to that the following method in UIInput superclass ...
#Override
public Object getSubmittedValue() {
if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) {
return "";
} else {
return submittedValue;
}
}
... is not overridden in UISelectMany superclass in such way that it returns new String[0] instead of "". This was an oversight during implementing Faces issue 671.
I have fixed it in Mojarra issue 5081.
In the meanwhile, until you can upgrade to the Mojarra version containing the fix, you can temporarily work around it by copy pasting the entire source code file of UISelectMany into your project while maintaining the package and adding the following method to it:
#Override
public Object getSubmittedValue() {
Object submittedValue = super.getSubmittedValue();
return "".equals(submittedValue) ? new String[0] : submittedValue;
}
I am trying to update a primefaces component after the 'isAdmin' value changes.
I am using the following HTML page:
<h:form id="form">
<p:tabView id="tabs">
...
<p:tab title="Admin" rendered="#{userSession.isAdmin}">
...
</p:tab>
</p:tabView>
</h:form>
My UserSession class:
#ManagedBean(name="userSession")
#SessionScoped
public class UserSession {
.
.
.
public boolean isAdmin;
public UserSession() {
isAdmin = false;
}
public void addRole(String role) {
if (role.equals("ADMIN") {
this.isAdmin = true;
}
role.add(role)
}
}
The addRole() method gets called and adds the role "ADMIN". I know this isn't a good way of doing things, but I'm only using it for testing purposes at the moment.
Then, under the class where I handle login and the addRole() method gets called, I am trying to update the component using:
RequestContext.getCurrentInstance().update("form");
I have been trying to update the form as I believe this is always visible in the DOM?
My issue is, is that this is simply not working and nothing is being updated.
Any help is greatly appreciated.
I figured out that I was always actually changing my UserSession object instead of the actual session bean itself.
Therefore, I did the following and just pointed to my bean instead:
// Previously
// UserSession us = new UserSession();
UserSession us = (UserSession) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("userSession");
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
= )
I'm going through validation in JSF and I see lots of examples of very basic logic. Frankly, I put them in the same category where the HelloWorld examples go. I can't imagine placing error messages in xhtml files, using a separate validation method for each validated field or employing bean validation.
What I want to do, is have a single method on the backing bean that will execute validation for each field, logging error messages driven by keys in i18n property files.
Can this be done? If so, how do we register that method as validating method, how do we obtain submitted field values for evaluation, and how do we register error messages?
<h:inputText id="username" value="#{bean.username}" label="UserName" binding="#{bean.component}"/>
<h:message for="username" />
<h:commandButton value="Submit" action="#{bean.actionMethod}" />
In your bean class,
private UIComponent component;
public UIComponent getComponent() {
return component;
}
public void setComponent(UIComponent component) {
this.component = component;
}
public String actionMethod() {
if (!validate()) {
return null;
}
// do your action method logic
}
private boolean validate() {
FacesContext context = FacesContext.getCurrentInstance();
//do validation for your fields and add to faces messages
FacesMessage msg = new FacesMessage(severity, summary, detail);
context.addMessage(component.getClientId(), msg);
// do for other fields
return status;
}
Refer this to get component client id
How to add a message to a specific component from JSF backing bean
I have a selectOneListBox and a button
<p:selectOneListbox id="somelistBox" value="#{bean.selectedItem}" styleClass="listBox">
<f:selectItems value="#{bean.list}" var="item" itemValue="#{item}" itemLabel="#{preparer.prepare(item)}"/>
</p:selectOneListbox>
<p:commandButton id="somebutton" process="#(.listBox)" update="#(.listBox)"/>
and a bean
#Component
public void class Bean
{
private List<Item> list;
private Item selectedItem;
public List<Item> getList()
{
return list;
}
public void getSelectedItem()
{
return selectedItem;
}
public void setSelectedItem(Item selectedItem)
{
this.selectedItem = selectedItem;
}
}
However, when I press the button the validation fails on selectOneListBox during the ajax post. From what I've read this can happen when you are trying to set the bound object in the bean with the wrong type of object, however my selectOneListBox contains a list of "Items" and the value is selectedItem which is also an "Item" so I can't see where I'm going wrong.
I think my problem was borne out of a misunderstanding of how browers work. They have no knowledge of java objects, just strings, so attempting to pass the objects back and forwards between browser and server was impossible. Instead I have changed the item value to the id of the item and then had to retrieve this object on the back end with a database call, ie
<p:selectOneListbox id="somelistBox" value="#{bean.selectedItem}" styleClass="listBox">
<f:selectItems value="#{bean.list}" var="item" itemValue="#{item.id}" itemLabel="#{preparer.prepare(item)}"/>
</p:selectOneListbox>
and then some call on the back end to get the object back from the id that is passed by the ajax post (I chose to do this in a Converter).