I'm trying to add a new row in a Primefaces datatable, then I would like to submit the content of this table and do some business logic. The datatable model is a collection that is maintained in a ViewScoped managed bean.
I'm using JSF 2.1 with Primefaces 3.3.
Short example:
<h:form id="my-form">
<p:dataTable value="#{testBean.list}" var="s" id="datatable">
<p:column>
<h:inputText value="#{s}"/>
</p:column>
<f:facet name="footer">
<p:commandButton value="Add row" action="#{testBean.addRow()}" process="#form" update="#form" immediate="true" />
<p:commandButton value="Do stuff" action="#{testBean.doSomeLogic()}" process="#form" update="#form"/>
</f:facet>
</p:dataTable>
</h:form>
Managed Bean:
#ManagedBean
#ViewScoped
public class TestBean implements Serializable {
private List<String> list;
public TestBean() {
}
#PostConstruct
public void init() {
list = new ArrayList<String>();
list.add("one");
list.add("two");
}
public void addRow(){
list.add(new String());
}
public void doSomeLogic(){
for (String string : list) {
System.out.println(string);
}
}
// getters and setters
}
What actually happens:
the user clicks on "add row" button, a new row is added (I need immediate to be true so no validation is done, those fields are part of a bigger form).
the user clicks on "do stuff", the collection has the right size (with new rows) but the user's input in not taken into account (neither modification to pre exiting rows, nor new values in freshly added rows).
What can I do to submit the new values too? I'm only beginning JSF and I'm not sure I'm already 100% getting it.
Thanks for your help.
Possible duplicates:
Add a row to h:dataTable via AJAX with request-scoped bean without losing the row data
How to Dynamically add a row in a table in JSF?
JSF datatable: adding and removing rows clear rows values
Edit: problem is solved thanks to Jitesh, a working example can be found here: JSF2, can I add JSF components dynamically?
The only problem is you are using immutable object in inputText. To understatnd this check out BaluC's Answer
According to it "As being an immutable object, the String doesn't have a setter method. The will never be able to set the entered value."
Try to remove immediate attribute from the commandButton you will find that on insertion of each row the data will be cleared.
If I understand correctly, there are some validations elsewhere in the form that are failing. When any of the submitted form values fail validation then none of the submitted values are applied to the managed bean unless immediate is used. This is why it seems that you are able to add a new row but not with the doStuff method. You did not add immediate to doStuff.
But stating that there are a few things you could do much more cleanly and efficiently.
First, the action attribute should really be used for navigation actions. JSF expects that methods bound to an action have a return value that represents the navigation result. For void methods it is better to use actionListener. For more information on the difference between action and actionListener read here: Differences between action and actionListener
Secondly, why not just set process and update to only the data table component and then you don't have to worry about other form validations? Here is an example:
<h:form id="my-form">
<p:dataTable value="#{testBean.list}" var="s" id="datatable">
<p:column>
<h:inputText value="#{s}"/>
</p:column>
<f:facet name="footer">
<p:commandButton value="Add row" actionListener="#{testBean.addRow}"
process=":my-form:datatable" update=":my-form:datatable" />
<p:commandButton value="Do stuff" actionListener="#{testBean.doSomeLogic}"
process=":my-form:datatable" update=":my-form:datatable" />
</f:facet>
</p:dataTable>
</h:form>
Related
I want to update a button in the header facet of a primefaces datatable and it does not work. I copied the button outside the datatable everything works fine.
The update should happen when the filter event of the datatable gets fired. I explicitly update the datatable, the outside- and the inside-button.
The intention is to display a button with an icon when no filter is set and another icon when a filter is used. In this example I simplyfied the use case: when no filter is used there is a open-lock icon, if I type something in a filter a closed-lock icon should be displayed. To release the lock one has to click the button (I did not implement the deletion of the filter in the datatable).
From what I understand I use the correct ID of the button inside the header. So I do not know why this does not work?
I am using mojarra 2.2 and primefaces 6.
<h:form id="id_form">
<p:dataTable
id="id_table"
value="#{stateController.names}"
var="currentName">
<p:ajax
event="filter"
listener="#{stateController.markLocked()}"
update="id_table id_form:id_table:id_button_inside id_form:id_button_outside"/>
<p:column
filterBy="#{currentName}"
filterMatchMode="contains">
<f:facet name="header">
<p:commandButton
id="id_button_inside"
action="#{stateController.markUnlocked()}"
icon="#{stateController.locked ? 'ui-icon-locked' : 'ui-icon-unlocked'}"
update="id_form"/>
</f:facet>
<h:outputText value="#{currentName}" />
</p:column>
</p:dataTable>
<p:commandButton
id="id_button_outside"
action="#{stateController.markUnlocked()}"
icon="#{stateController.locked ? 'ui-icon-locked' : 'ui-icon-unlocked'}"
update="id_form"
/>
</h:form>
#Named(value = "stateController")
#SessionScoped
public class StateController implements Serializable
{
private boolean locked;
private List<String> names;
#PostConstruct
private void init()
{
locked = false;
names = new ArrayList<>();
names.add("peter");
}
public void markLocked()
{
locked = true;
}
public void markUnlocked()
{
locked = false;
}
// getter + setter omitted
}
I also tried to put a button in a separate column. With this button (which is displayed in every row of the datatable) everything works fine as well.
It's a bit late, but maybe someone will find it useful some day.
To solve Filou's problem, you need to define remoteCommand outside dataTable and make it update dataTable's header facet.
<p:remoteCommand name="rmtCommand" update="id_form:id_table:id_button_inside"/>
<p:dataTable
id="id_table"
value="#{stateController.names}"
var="currentName">
<p:ajax
event="filter"
listener="#{stateController.markLocked()}"
oncomplete="rmtCommand()"/>
I'm doing jsf with primefaces5.0 and here's my scenario:
I have a page contains a dataTable with contextmenu like this:
When I clicked the contextmenu, I want to popup another "window" (not "tab") with information about the selected data, for example, the String "data1".
However, it can't be done whether I use action or url parameter in the p:menuitem.
When I use action parameter, anotherPage shows but in the original window, while the opened new window is empty:
And if I change action="/anotherPage" into url="/anotherPage.xhtml",anotherPage shows in the new window but with no information about the selected data:(Please note that the title has changed into "Another Page")
Here's what I've done:
Facelet:
<h:form>
<p:contextMenu for="dataTable">
<p:menuitem value="clickMe" icon="ui-icon-gear"
onclick="window.open('', 'Popup', config = 'scrollbars=yes,status=no,toolbar=no,location=no,menubar=no,width=1300,height=370').focus();"
target="Popup" actionListener="#{mainBean.showPopup()}" action="/anotherPage"/>
</p:contextMenu>
<p:dataTable id="dataTable" value="#{mainBean.dataList}" var="data" rowIndexVar="index" emptyMessage="Loading..."
selectionMode="single" selection="#{mainBean.selectedStr}" rowKey="#{data}">
<p:column headerText="data">
<p:outputLabel value="#{data}"/>
</p:column>
</p:dataTable>
</h:form>
BackingBean:
private List<String> dataList=new ArrayList<>();
private String selectedStr="";
public void showPopup(){
Map<String, Object> reqMap=FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
reqMap.put("param", selectedStr);
}
AnotherPage.xhtml:
<p:outputLabel value="This is the selected String:"/>
<p:outputLabel value="#{anotherPageBean.txt}"/>
AnotherPageBean:
private String txt;
#PostConstruct
public void init(){
Map<String, Object> reqMap=FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
txt=(String) reqMap.get("param");
}
Thank you very much.
Update
I did a lot survey and find out something:
Target attribute of menuitem is not rendered for action, it's a known issue and won't be fixed.
Menuitem supports f:params to pass parameters according to optimus.prime.
When I use url, the parameters can't be find in request map, which probably means it's a brand new request so that I can't find what I put in the reqMap.
Maybe I can put parameters in session map instead of request map because of what I mentioned in 3.
Neither action nor actionListener be called if I use url.
According to what I've said in the update section of my question, I found a workaround.
Please note that I believe this is just a workaround, hope there are some normal ways to solve it.
As target attribute of menuitem is not rendered for action I can only use url.
So I tried using f:params to pass parameters with url but still in vain because the 3rd point I mentioned in the update section I think.
After that, I tried put parameters in session map through backing bean and use action to call the method. However, it still won't work because action is not called if I use url.
According to this thought, I call the method outside the menuitem.
I add a event to the dataTable and call method there:
<p:dataTable id="dataTable" value="#{mainBean.dataList}" var="data" rowIndexVar="index" emptyMessage="Loading..."
selectionMode="single" selection="#{mainBean.selectedStr}" rowKey="#{data}">
<p:ajax event="contextMenu" listener="#{mainBean.setParam()}"/>
<p:column headerText="data">
<p:outputLabel value="#{data}"/>
</p:column>
</p:dataTable>
And in the Backing Bean:
public void setParam(){
HttpSession session=(HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
session.setAttribute("param", selectedStr);
}
So I can get info through session in anotherPageBean like:
#PostConstruct
public void init(){
HttpSession session=(HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
txt=(String) session.getAttribute("param");
session.removeAttribute("param");
}
Please note that the attribute should be removed after the info is got.
And this is the result:
Just for those who have the same problem.
Assume we have a form. One p:inputText visible but user can add many more using p:commandButton. All this values have to be provided when submitting with another p:commandButton. Issue arises when user tries to add more than one empty input fields. All of them are marked required="true" so validation error appears when one field is empty and user try to add another.
The best would be to allow to add as many fields as user needs, then fill them in and submit.
JSF:
<h:form id="myForm">
<p:commandButton value="add" actionListener="#{testBean.addNewItem()}" update="#form"/>
<p:commandButton value="done" update="#form,:p"/>
<br/>
<ui:repeat value="#{testBean.list}" var="l">
<p:inputText value="#{l.name}" required="true"/>
<br/>
</ui:repeat>
</h:form>
<p:messages autoUpdate="true"/>
<p:panel id="p">
#{testBean.list}
</p:panel>
Backing bean does nothing fancy. Only provides getter and setter for list. It also adds empty string to the list.
#ManagedBean
#ViewScoped
public class TestBean implements Serializable {
private List<Item> list = new ArrayList<Item>();
public List<Item> getList() { return list; }
public void setList(List<Item> list) { this.list = list; }
public void addNewItem() { list.add(new Item()); }
}
I could:
Remove requirement for field - not an option.
Add immediate="true" for adding button. Validation is not a problem now but it causes all values that was filled in but not submitted to disappear. And I need to update #form because only then newly added fields will be rendered by ui:repeat.
I tried to add process="#this" for adding button. Unfortunately that didn't change a thing. Input field values are not processed, but form needs to be updated. I am loosing not submitted values as above.
What am I missing? Is there any workaround?
Just let the required attribute check if the "done" button is pressed. The button's own client ID is present as a request parameter if that's the case. Request parameters are available by #{param} mapping. You can use button's binding attribute to bind the physical component to the view so that you can grab its UIComponent#getClientId() elsewhere. Finally just do the boolean logic.
E.g.
<p:commandButton binding="#{done}" ... />
...
<p:inputText ... required="#{not empty param[done.clientId]}" />
Will something like this work?
<p:inputText value="#{l.name}" required="#{l.name != null ? true : false}"/>
This will enable the newly added inputText components to not be required but enforce the items already in the list to be required.
I have a datatable that iterates over a list, lets call it myList. I populate this myList based on some request parameters. Inside the datatable there are commandLinks. If i put a dummy entry into myList during apply request values phase, i can click on the first commandLink, and it works as it should (it is executed during invoke application phase, and by then the correct entries are in myList). If i dont do it, or i click on the second or later commandLink, nothing happens. So im guessing the clientId of the command button is resolved during apply request phase, even thought it is only used during the invoke application phase, which results in the broken commandLinks.
something like this:
<h:selectManyCheckbox styleClass="hidden"
value="#{cc.attrs.selectionList.selected}"
converter="#{cc.attrs.converter}" >
<f:selectItems value="#{cc.attrs.selectionList.all}"
var="item" itemValue="#{item}" itemLabel="" />
</h:selectManyCheckbox>
<h:dataTable value="#{cc.attrs.selectionList.selectedTest}" var="item">
<h:column>
<h:commandLink value="deselect" action="#{cc.attrs.selectionList.deSelect(item)}">
<f:ajax execute=":#{component.parent.parent.parent.clientId}"
render=":#{component.parent.parent.parent.clientId}" />
</h:commandLink>
</h:column>
</h:dataTable>
and the model:
public List<E> getSelected()
{
return myList;
}
public List<E> getSelectedTest()
{
if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.RESTORE_VIEW) && getSelectedList().isEmpty())
{
return Collections.singletonList(myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES) && getSelectedList().isEmpty())
{
return Collections.nCopies(2, myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.PROCESS_VALIDATIONS) && getSelectedList().isEmpty())
{
return Collections.nCopies(3, myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES) && getSelectedList().isEmpty())
{
return Collections.nCopies(4, myList.get(0));
}
return myList;
}
public void deSelect(E item)
{
myList.remove(item);
}
with this example, the top two commandLinks of the datatable works.
My question is why is this behaviour, and is there any way around without filling myList with dummy entries? I do not want to use any (viewscoped) backing bean to store the data.
During apply request values phase, JSF needs to iterate over the model in order to find the clicked command link. If the model changes incompatibly during the HTTP request wherein the form submit is processed (the postback) as compared to the initial HTTP request wherein the table with the command links is shown, then JSF may not be able to find the clicked command link and thus never queue the desired action, or the object representing the "current row" is not the same as the enduser intented.
If your bean is request scoped, then it should be written in such way that it initializes selectedTest in the constructor or #PostConstruct method based on some request parameter. At least, you should absolutely not perform business logic in getters.
You can pass the parameters necessary for reconstructing the selectedTest as <f:param> in the command link.
<h:commandLink ...>
<f:param name="some" value="#{bean.some}" />
</h:commandLink>
And prepare the model as follows:
#ManagedProperty
private String some;
#PostConstruct
public void init(){
selectedTest = populateItBasedOn(some);
}
// Don't change standard getters/setters!
See also:
commandButton/commandLink/ajax action/listener method not invoked or input value not updated - point 4
I managed to get my way around by binding the selectManyCheckbox itself to my componentBindings HashMap, and using that for the dataTable (with immediate="true" on the selectManyCheckbox):
<h:selectManyCheckbox immediate="true" styleClass="hidden"
binding="#{componentBindings[cc.attrs.selectionList]}"
value="#{cc.attrs.selectionList.selected}"
converter="#{cc.attrs.converter}" >
<f:selectItems value="#{cc.attrs.selectionList.all}" var="item"
itemValue="#{item}" itemLabel="" />
</h:selectManyCheckbox>
<h:dataTable value="#{componentBindings[cc.attrs.selectionList].value}" var="item">
<h:column>
<h:commandLink value="deselect" action="#{cc.attrs.selectionList.deSelect(item)}">
<f:ajax execute=":#{component.parent.parent.parent.clientId}"
render=":#{component.parent.parent.parent.clientId}" />
</h:commandLink>
</h:column>
</h:dataTable>
in faces-config.xml:
<managed-bean>
<description>Holder of all component bindings.</description>
<managed-bean-name>componentBindings</managed-bean-name>
<managed-bean-class>java.util.HashMap</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
This odd behavior only happen in Firefox (specifically Firefox 8). So I have a dataTable that I can do multiple selection. A submit button, that will display a list of selected items to a dataList and to a dialog. If the user did not select anything, then a error msg come up asking the user to select something. The dialog will not appear if the user select nothing. The below code does all that. However FireFox behaves oddly if you do these follow:
Click to select an item on the dataTable
Then refresh (F5 or Ctl + R) the page (you can see the selection got clear off)
Then click submit, it show whatever I just selected.
This is unexpecting, since the refresh should clear out whatever you just select due to nature of #ViewScoped bean. This behavior only happen in Firefox. IE 8 behave correctly for me. Is this a bug, or am I doing something wrong here?
Mojarra 2.1 + PrimeFaces3.0 Final + Tomcat 7
UPDATE: I did some debugging, when I refresh page, the value of the array selectedFoods become null, but for some odd reason, when it get to public void checkSelection(), it hold the value of the previous selection. So odd.
Here is my code.
<p:growl id="messages" showDetail="true" />
<p:messages id="msgs"/>
<h:form id="form">
<p:dataTable value="#{viewBean.foodList}" var="item"
selection="#{viewBean.selectedFoods}"
selectionMode="multiple"
rowKey="#{item}">
<p:column>
#{item}
</p:column>
<f:facet name="footer">
<p:commandButton value="Submit" update=":form:display :dataList"
action="#{viewBean.checkSelection}"/>
</f:facet>
</p:dataTable>
<p:dataList id="display" value="#{viewBean.selectedFoods}" var="item"
itemType="disc">
#{item}
</p:dataList>
</h:form>
<p:dialog id="dialog1" widgetVar="dialog1" dynamic="true" width="200">
<p:dataList id="dataList" value="#{viewBean.selectedFoods}" var="item"
itemType="disc">
#{item}
</p:dataList>
</p:dialog>
Here is my managed bean
#ManagedBean
#ViewScoped
public class ViewBean implements Serializable {
private List<String> foodList;
private String[] selectedFoods;
#PostConstruct
public void init() {
foodList = new ArrayList<String>();
foodList.add("Pizza");
foodList.add("Pasta");
foodList.add("Hamburger");
}
public void checkSelection(){
RequestContext requestContext = RequestContext.getCurrentInstance();
if(selectedFoods.length > 0){
requestContext.execute("dialog1.show()");
}else{
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Error", "Please select"));
requestContext.addPartialUpdateTarget("messages");
}
}
//setter, getter
}
Your code is fine. What you're seeing is because of something that is supposed to be a feature of Firefox (I was able to reproduce this on FF4). The selection model for p:dataTable is implemented with a hidden form field. When reloading a page, Firefox tries to save and restore form field values that have changed so that you don't lose what you entered. You can observe this by adding a <h:inputText/> to your view, typing something in the input, and reloading.
I'm not sure that the Firefox team meant for this to apply to hidden form fields, but I figure there's a decent chance that they did. I plan to file a bug report with Primefaces to either initialize the hidden input or to read the input on load to make the p:dataTable selection match. Either solution should result in the rendered selection and the hidden selection model to be in sync.