JSF refreshing list via Ajax incorrectly - jsf-2

I am trying to migrate some functionality from JQuery to JSF and I am getting strange results.
Basically it is a page where you can navigate links (imagine like a Windows Explorer on the Web). Folders have a link and leafs don't. To construct this, it is used a private List<Element> list;, which is refreshed accordingly. So when the user clicks on a "folder", a new list is created with the contents of that folder.
The problem is that I am getting strange results. Sometimes I navigate a link and it is refreshed correctly, but in some cases it behaves like the page is reloaded (but it is not) and the list is reset to its initial values (?). For example, I navigate to "name2" and get the right things. But then I go to "name2b" and I instead of the leafs I get "name1", "name2" and "name3", which are only set in the init() method. I don't know how this happens, but exploring the source code it seems that the c:foreach structure is messing with the ids and an old ID is rendered in the page (note that I have tried ui:repeat with same incorrect results).
How can I fix this?
This is the simplified code:
<f:metadata>
<f:viewAction action="#{controller.init}" />
</f:metadata>
...
<h:panelGroup id="visor" layout="block">
<ul>
<c:forEach items="#{controller.list}" var="element">
<li>
<ui:fragment rendered="#{element.folder}">
<h:form>
<h:commandButton action="#{controller.navigate(element.name)}" type="submit" value="#{element.name}" >
<f:ajax render="visor" execute="#this" />
</h:commandButton>
</h:form>
</ui:fragment>
<ui:fragment rendered="#{not element.folder}">
<span>#{element.name}</span>
</ui:fragment>
</li>
</c:forEach>
</ul>
</h:panelGroup>
And the controller
#Named(value = "controller")
#ViewScoped
public class MyController implements Serializable {
private List<Element> list;
public void init() { //Called when loading the page
list = new ArrayList<>();
Element e1 = new Element("name1", true); //name and isFolder?
Element e2 = new Element("name2", true);
Element e3 = new Element("name3", true);
list.add(e1);
list.add(e2);
list.add(e3);
}
public void navigate(String name) {
list = new ArrayList<>();
if (name.equals("name1")) {
Element e1 = new Element("name1a", true);
Element e2 = new Element("name1b", true);
Element e3 = new Element("name1c", true);
list.add(e1); list.add(e2); list.add(e3);
} else if (name.equals("name2")) {
Element e1 = new Element("name2a", true);
Element e2 = new Element("name2b", true);
Element e3 = new Element("name2c", true);
list.add(e1); list.add(e2); list.add(e3);
} else {
Element e1 = new Element("leaf1", false);
Element e2 = new Element("leaf2", false);
list.add(e1); list.add(e2);
}
}
//....
EDIT
My problem seems to be described in here.
I have to investigate if this question can bring some light to the subject.

Apparently it is a well-known thing that my structure cannot be dynamically updated in JSF. The solution I have to make is to use a h:datatable instead of a <ul> list (this is one thing I really dislike of JSF, but I am not sure how to render a list with basic JSF - instead of a table, that I can dynamically update.
The Ajax render attribute had to specify the form id.
<h:form id="visor">
<h:dataTable value="#{controller.list}" var="element">
<h:column>
....
<h:commandButton action=.... >
<f:ajax render="visor" execute="#this" />
</h:commandButton>
....
</h:column>
</h:dataTable>
</h:form>
Edit: The UL also worked, as long as I render the form and the form contains everything

Related

update component in another xhtml form from Custom layout p:selectOneRadio not working

My xhtml is split in to Menu area (defaultMenu.xhtml) and Content area (defaultContent.xhtml).
The code for defaultMenu.xhtml is:
<h:form id="defaultmenuform">
<p:outputPanel id="menupanel" class="contain auto-fixed-center">
<p:panel id="pmenu" visible="#{phController.user.menuVisible}">
<table id="stutable">
<tr>
<td width="15%">
<p:outputLabel id="stuname" value="#{phController.phBean.studentName}" />
</td>
<td>
<p:tabMenu activeIndex="#{param.selectedtab}">
<p:menuitem value="Home" outcome="phome" icon="ui-icon-star">
<f:param name="selectedtab" value="0" />
</p:menuitem>
<p:menuitem value="Bank" outcome="bhome" icon="ui-icon-person">
<f:param name="selectedtab" value="1" />
</p:menuitem>
</p:tabMenu>
</td>
</tr>
</table>
</p:panel>
</p:outputPanel>
</h:form>
The defaultContent.xhtml actually displays the ph.xhtml content (as part of functional navigation) and the code is:
<ui:define name="content">
<f:event listener="#{phController.readPeople}" type="preRenderView">
</f:event>
<h:form id="form">
<p:selectOneRadio id="selstud" value="#{phController.phBean.ssSeq}" layout="custom">
<p:ajax update=":defaultmenuform:parentmenupanel :defaultmenuform:stuname" listener="#{phController.onChangePerson}"/>
<f:selectItems value="#{phController.selectStudents}" />
</p:selectOneRadio>
<div style="width: 300px; float:left;">
<p:dataGrid var="studentlist" value="#{phController.listStudent}" columns="1" rowIndexVar="stuindex">
<p:panel header="" style="text-align:left">
<h:panelGrid columns="1" style="width:100%">
<h:outputText value="#{studentlist.studentName}" />
<p:radioButton for=":form:selstud" itemIndex="#{stuindex}"/> Select
</h:panelGrid>
</p:panel>
</p:dataGrid>
</div>
</h:form>
</ui:define>
The code for backing bean is:
Map<String, Object> studentparam = new HashMap<>();
studentparam.put("studentSeq", phBean.getSsSeq());
lS = getBaseDAOService().readStudent("readStudent", studentparam);
phBean.setStudentName(lS.get(0).getStudentFirstName() + " " + lS.get(0).getStudentLastName());
As you can see, I am calling the onChangeStu method to display the selected Student Name in defaultMenu.xhtml. I am using Custom Layout p:selectOneRadio in ph.xhtml and onClick trying to update a p:outputLabel in defaultMenu.xhtml.
The backing bean method gets invoked successfully and the value is also set in variable phController.phBean.studentName, but the update is not working. I also checked using view source and the id is “:defaultmenuform:stuname”, I also tried updating the menu panel ":defaultmenuform:menupanel”, but none of this works.
Not sure how to resolve this. Please suggest.
Including the structure of all .xhtmls
<h:body id="entirePageBody">
<div id="page">
<ui:insert name="header" >
<ui:include src="/template/defaultHeader.xhtml" />
</ui:insert>
<ui:insert name="menu" >
<ui:include src="/template/defaultMenu.xhtml" />
</ui:insert>
<div id="content_div" class="auto-fixed-center">
<div id="content_div_padding" class="content-block">
<ui:insert name="content" >
<ui:include src="/template/defaultContent.xhtml" />
<ui:debug hotkey="z" />
</ui:insert>
</div>
</div>
<ui:insert name="footer" >
<ui:include src="/template/defaultFooter.xhtml" />
</ui:insert>
</div>
</h:body>
PhController.java:
public class PhController extends BaseController implements Serializable {
private List<Stud> listStudent;
private List selectStudents;
SelectItem option;
private PhBean phBean;
private Boolean menuVisible;
int counter = 0;
public PhController() {
phBean = new PhBean();
}
public void readPeople() {
listStudent = new ArrayList<Stud>();
listStudent.add(new Stud(1, "John Miller"));
listStudent.add(new Stud(2, "Scott Jackson"));
selectStudents = new ArrayList();
option = new SelectItem(listStudent.get(0).getStudentSeq(), "Select");
selectStudents.add(option);
option = new SelectItem(listStudent.get(1).getStudentSeq(), "Select");
selectStudents.add(option);
phBean.setSsSeq(String.valueOf(1));
phBean.setSelectedName(listStudent.get(0).getStudentName());
menuVisible = true;
}
public void onChangePerson() {
phBean.setSelectedName(listStudent.get(1).getStudentName());
}
// Getters and Setters
}
PhBean.java:
public class PhBean implements Serializable {
private String ssSeq;
private String studName; // Used to display the name in the Menu bar.
private String selectedName;
public PhBean() {
}
// Getters and Setters
}
I'd say that in the p:ajax in defaultContent.xhtml the list of components to be updated should be separated with spaces only, no commas - so try changing this:
update=":defaultmenuform:menupanel, :defaultmenuform:stuname"
to this:
update=":defaultmenuform:menupanel :defaultmenuform:stuname"
UPDATE
I played with this a bit more and may have found a clue - please add the following code to defaultmenuform:
<p:messages autoUpdate="true" showDetail="true" />
This should help us tracking the reason for failed validation (in case failing validation is the root cause for you - as I said, I have rather limited possibility to reproduce this issue).
Anyway, when I selected some item in p:selectOneRadio, an error message like this appeared:
Conversion Error setting value 'test001.Student#4110c95c' for 'null Converter'.
And the root cause was on this row:
<p:selectOneRadio id="selstud" value="#{phController.phBean.ssSeq}" layout="custom">
p:selectOneRadio expects only String to be passed as a value - and ssSeq is very likely of a different type. Try to change the way value is populated to ensure it is always String - maybe a different attribute of the phBean or simply a brand new String one.
NOTE: if this doesn't help, maybe you could update your question with very simplified example of how phController and phBean could look like if we are to test it.
UPDATE #2
So you have explained there is a problem that you want to call phController.readPeople every time the page is loaded/refreshed, but instead it gets loaded with each and every Ajax request, thus overwriting the values.
In your PhController (it is a bean, right? session scoped?) you could add something like this (omitted null checks for the sake of readability):
public void readPeopleOnGet() {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
HttpServletRequest req = (HttpServletRequest) ec.getRequest();
String reqMethod = req.getMethod();
if ("GET".equals(reqMethod)) {
readPeople();
}
}
With the above method you could keep this part of your defaultContext.xhtml in place, provided it is actually called (I assume so), just with the listener method changed:
<f:event listener="#{phController.readPeopleOnGet}" type="preRenderView">
</f:event>
The method readPeopleOnGet will still be called with every request to the page, but since Ajax requests are POST, it will only call readPeople when the page is loaded or refreshed as whole.
This may not be a "perfectly clean" solution, but seemed to work properly when I tested it.
UPDATE #3
Well, since you use PrimeFaces, it would be possible to identify Ajax call also this way:
public void readPeopleOnGet() {
RequestContext rc = RequestContext.getCurrentInstance();
if (!rc.isAjaxRequest()) {
readPeople();
}
}
But if I got your point from latest comments, you want to only run readPeople when the page is loaded for the very first time - so the following part could be even better for that.
You didn't answer if PhController is actually a bean, but I assume it is (there were no annotations visible from the code you posted). You may try making it #SessionScoped:
#ManagedBean
#SessionScoped
public class PhController extends BaseController implements Serializable {
// the rest of the PhController goes here...
Then you could add something like this to the PhController:
#PostConstruct
private void init() {
readPeople();
}
The annotation #PostConstruct ensures the method init is called exactly once after the bean was created. You can continue calling the method readPeople from other places as necessary, while removing both the <f:event ... /> and readPeopleOnGet as these will no longer be needed.

Primefaces Datatable filter values are lost after the datatable is refreshed

I am using JSF 2.0 and Primefaces 3.2. I have a main form with a datatable and a button on each row that brings up a dialog with another data table. The dialog has its own form. When the datatable row is selected in the dialog, i process the selection and then refresh the datatable in the main form. After the refresh all my filters on the main datatable are lost. Is there a way to remember the filter values?
My JSF page
<h:panelGroup id="mainTable">
<h:form id="mainForm" prependId="false">
<p:dataTable id="cl_grid1" var="item" widgetVar="mainGrid"
value="# {controller.items}">
<p:column headerText="Colum1" filterMatchMode="contains" filterBy="#{item.myObj.code}" sortBy="#{item.myObj.code}">
<h:outputText value="#{item.myObj.code}" style="float:left" />
<p:commandButton id="selectBtn" icon="select"
oncomplete="selectDialog.show()" actionListener="#{controller.setMainItem}">
<f:param name="selectedMainItem" value="#{item.id}"/>
</p:commandButton>
</p:column>
</p:dataTable
</h:form>
</h:panelGroup>
<p:dialog id="cl_selectDialog" header="Select AAAA" dynamic="true" modal="true" widgetVar="selectDialog"
height="500" width="900" showEffect="explode" hideEffect="explode">
<h:form id="selectForm" prependId="true">
<p:dataTable id="cl_selectGrid" var="selectorItem" widgetVar="selectGrid"
value="# {controller.selectorItems}" selection="#{controller.selectedItem}"
rowKey="#{selectorItem.id}" selectionMode="single">
<p:commandButton actionListener="#{controller.processSelection}" value="Ok" icon="ui-icon ui-icon-search"
oncomplete="selectDialog.hide()" update=":mainTable" process="#form"/>
<p:commandButton value="Cancel" icon="ui-icon ui-icon-search" onclick="selectDialog.hide()" type="button"/>
</h:form>
</p:dialog>
My Controller
In my controller all i am doing is hydrating my datatable again.
#Component
#Scope("view")
public class Controller extends BaseController implements Serializable {
private List<Item> items;
public Controller() {
items = new ArrayList<Item>();
}
#PostConstruct
public void init() {
loadItems();
}
protected void loadItems() {
items = //Load items from Database
}
public void processSelection(ActionEvent event) {
//Add the selected Item and then reload items
loadItems();
}
}
If it is an AJAX Request, you can add on your trigger component a 'oncomplete' method which has :
<p: ... update='myDatatableId' oncomplete='myDatatableWidget.filter();' />
By this way you will re-triggering current filters on the datatable or you can also clear all filters by using
... oncomplete='myDatatableWidget.clearFilters();' ...
We also came up with a slightly more elegant solution than the one posted by kolossus which is as simple as extending the PF Datatable for persistent filter values.
http://www.stickysession.com/?p=258
Since you must to refresh the table from within its form (so it will preserve the filter state) another solution could be to place a hidden button in the main table form and use it as a 'proxy' by clicking it from your dialog, that way the filter will be preserved, like this
change your button from
<p:commandButton actionListener="#{controller.processSelection}" value="Ok" icon="ui-icon ui-icon-search"
oncomplete="selectDialog.hide()" update=":mainTable" process="#form"/>
into this (just a simple button that clicks the hidden one)
<p:commandButton onclick="$('#my-proxy-button-id').click(); return false;" value="Ok" icon="ui-icon ui-icon-search"/>
and add a new button inside the main table form
<p:commandButton style="display:none;" id="my-proxy-button-id" actionListener="#{controller.processSelection}"
oncomplete="selectDialog.hide()" update="mainTable" process=":selectForm"/>
using this technique you wont be needed to call for filtering manually and no additional "blinking" will appear on screen

JSF CommandButton Show/Hide Issue

Im using a primefaces FieldSet component and have a button field specified inside the field set. I have a requirement, Where, when I click on the command button, a h:selectonemenu & an inputtext should be displayed and On further click, the h:selectOnemenu &inputtext should be hidden. Below is the code that I have written to show & hide. The show part works fine. But when I try to click on the button to hide, it does not work.
xhtml code
<h:panelGrid id="grid1" columns="3">
<p:fieldset id="GlobalAdjustment" legend="GlobalAdjustment" style="font-size:12px !important;width:30%" >
<h:commandButton id="globalAdjustID" image="#{review.imageUrl}" actionListener="#{review.showUpdateList}" />
</p:fieldset>
<h:selectOneMenu id="SelectID" value="#{review.reviewList}" rendered="#{demandReview.selectoneRenderer}">
<f:selectItems id="FilterSelectListID" value="#{review.reviewListIDs}" style="font-size:12px !important"></f:selectItems>
</h:selectOneMenu>
<h:inputText id="fieldUpdateID" required="false" rendered="#{review.inputTextRenderer}"></h:inputText>
</h:panelGrid>
ManagedBean ActionListener method
public void showUpdateList(ActionEvent event)
{
System.out.println("entering the Action Method:");
Map<String, Object> idMap = new HashMap<String, Object>() ;
idMap = event.getComponent().getAttributes();
String url = (String) idMap.get("image");
System.out.println("The url is :"+url);
if(url.equals("/images/add_data_button.png")){
//upbean.setImageUrl("/images/remove_data_button.png") ;
imageUrl ="/images/remove_data_button.png" ;
selectoneRenderer = true;
inputTextRenderer = true;
}else
{
imageUrl ="/images/add_data_button.png" ;
selectoneRenderer = false;
inputTextRenderer = false;
}
}
When I click the command button inside the fieldset for the first time, it works fine and displays the selectoneMenu & the textbox. Once further clicks, the ActionListener is not getting invoked. Please help me resolve the issue.
Thanks
You can try another approach. Use p:commandButton with an update attribute like this:
<h:panelGrid id="grid1" columns="3">
<p:fieldset id="GlobalAdjustment" legend="GlobalAdjustment" style="font-size:12px !important;width:30%" >
<p:commandButton id="globalAdjustID" image="#{review.imageUrl}" actionListener="#{review.showUpdateList}" update="grid1"/>
</p:fieldset>
<h:selectOneMenu id="SelectID" value="#{review.reviewList}" rendered="#{demandReview.selectoneRenderer}">
<f:selectItems id="FilterSelectListID" value="#{review.reviewListIDs}" style="font-size:12px !important"></f:selectItems>
</h:selectOneMenu>
<h:inputText id="fieldUpdateID" required="false" rendered="#{review.inputTextRenderer}"></h:inputText>
</h:panelGrid>
This way you'll use partial processing and the conditional renderization of the panel's inner components will be done through AJAX.
Also, it could be a good idea to check your server's console to see if there are any messages in the queue that were not displayed.
Does the requirement specify using the server side? For something like this, I would have thought a simple Javascript script would have been better suited.
For example, use the jQuery library to do something like:
<h:commandButton id="xyz" onclick="$('#elementId').toggle();"/>

JSF 2 check / uncheck list of h:selectBooleanCheckbox

I have a loop with a bunch of h:selectBooleanCheckboxs inside a ui:repeat and I want to be able to check / uncheck all of them with one click. Following this this is how I try to do it:
<h:selectBooleanCheckbox value="#{bean.selectAll}" valueChangeListener="#{bean.selectAllCustomers}">
<f:ajax execute="#form" render="entireLoop"/>
</h:selectBooleanCheckbox>
The loop:
<h:panelGroup id="entireLoop">
<ui:repeat var="customer" varStatus="status" value="#{bean.customers}">
<tr>
<td>
<h:selectBooleanCheckbox value="#{bean.customersMap[customer]}"/>
</td>
<td><h:commandLink value="#{customer.userID}" action="#{navBean.gotoEditCustomer(customer)}"/></td>
<td>#{bean.getContact(user)}</td>
<td>#{customer.companyName}</td>
<td>#{customer.email}</td>
<td>#{customer.phone}</td>
</tr>
</ui:repeat>
</h:panelGroup>
The code in the bean:
public void selectAllCustomers() {
Iterator<User> keys = customersMap.keySet().iterator();
while(keys.hasNext()) {
User user = keys.next();
customersMap.put(user, selectAll);
}
}
When I click to check the "select all" checkbox, I have two problems:
Nothing happens. Only when I click on somewhere else in the page the method is called. How can I get it to go directly?
The entire loop disappears from the page. Why? (I tried to get the ajax to render only the selectBooleanCheckbox by giving it an id, or a name, but I'm getting an error saying this is not a naming component.)
Instead of using valueChangeListener, your method will work if you use the listener attribute of the <f:ajax> tag. It would be something like this:
<h:selectBooleanCheckbox value="#{bean.selectAll}">
<f:ajax listener="#{bean.selectAllCustomers}" render="entireLoop"/>
</h:selectBooleanCheckbox>
Besides, you don't need to submit the whole form with execute="#form". You only need to submit selectAll property and re-render the entireLoop.
If you really want to use valueChangeListener, you must note that your method will be triggered before the new value is applied in the selectAll property. As a result, your entireLoop will not be updated until the next request. To use valueChangeListener, your listener method should look like this:
public void selectAllCustomers(ValueChangeEvent e) {
boolean newSelectAll = (Boolean) e.getNewValue();
Iterator<Users> keys = customersMap.keySet().iterator();
while(keys.hasNext()) {
Users user = keys.next();
customersMap.put(user, newSelectAll);
}
}

Submitting a JSF2 form does not reload the collection on the same page

JSF 2.0 (mojarra) application. I have a very trivial form for adding an item
<h:form>
#{msg['add custom title']}:<br />
<table>
<tr>
<td>#{msg['heaading']}:</td>
<td><h:inputText value="#{titlesBean.title.heading}"></h:inputText></td>
</tr>
<tr>
...
</tr>
</table>
<h:commandButton action="#{titlesBean.addTitle}" value="#{msg['g.save']}" />
</h:form>
And then on the same page I have a list of all the items already added:
<h:dataTable id="manualTitlesForm" value="#{titlesBean.manualTitles}" var="title" border="1" cellspacing="0">
<h:column>
<f:facet name="header">#{msg['heaading']}</f:facet>
#{title.heading}
</h:column>
...
<h:column>
<f:facet name="header">#{msg['actions']}</f:facet>
<h:form>
<h:commandButton action="#{titlesBean.editManualTitle(title)}" value="#{msg['g.edit']}" />
<h:commandButton action="#{titlesBean.deleteManualTitle(title.id)}" value="#{msg['g.delete']}" />
</h:form>
</h:column>
</h:dataTable>
The code in the bean code is super simple:
#Controller
#Scope(Scopes.REQUEST)
public class TitlesBean {
private List<JTitle> manualTitles;
#PostConstruct
private void init() {
this.manualTitles = titlesManager.getManualTitles();
}
public String addTitle() {
title.setCreated(new Date());
title.setManual(true);
try {
titlesManager.addTitle(title);
title = new JTitle();// this is added, delete from the variable. only if no exception though !!!
UserMessagesBean.addMessage("saved");
} catch (Exception e) {
UserMessagesBean.setValidationException(e.getMessage());//different exception added
}
return null;
}
public List<JTitle> getManualTitles() {
return manualTitles;
}
}
Now the problem is that getManualTitles() is called as many times as the number of titles I have, which causes for example 12 calls to the DB, instead of one. Why is this happening is beyond my understanding. I can fix this with caching the manual titles in the bean. This is not my main problem.
The problem is that addTitle() is called AFTER getManualTitles(). In fact getManualTitles() is called for example 10 times, then addTitle(), then two more times the getManualTitles() method. This makes me think that this is some kind of parallel execution which causes my page to show only the 12 old records instead of 13. I have to reload the page, then 13 is shown.
UPDATED: now caches the list. Problem still not solved.
WHY? How can I fix this?
This is a quick fix, but not a real solution. Redirect the result of addTitle():
Add the following to addTitle():
...
FacesContext.getCurrentInstance().getExternalContext()
.redirect("manualTitles.jsf");
return null;
}

Resources