I have a datatable that is initially populated with no data (an empty list). There is a commandButton in the footer of the table that adds rows to the table. When a row is added, the last cell in the row is another commandButton to delete the row. My issue is that the delete button keeps deleting the first row in the table regardless of which row is selected.
Example: if I add 3 rows to the table and hit the delete button in the last cell of the second row, action="#{addAccountBean.deleteFixVendor(dataItem)}" passes the first row to the deleteFixVendor method instead of the second row which was clicked. I can't figure out why the object for the first row keeps on getting passed to the delete method regardless of which row button is clicked.
My JSF code:
<h:form>
<f:validateBean disabled="#{!empty param['disableValidation']}" >
<p:panel id="panel" header="Add New Account" >
<p:dataTable var="dataItem" value="#{addAccountBean.account.vendors}" >
<p:column headerText="Vendor" >
<p:selectOneMenu value="#{dataItem.vendor}">
<f:selectItems value="#{addAccountBean.menu.fixOption}" />
</p:selectOneMenu>
</p:column>
<p:column headerText="Monthly Cost" >
<p:inputText value="#{dataItem.cost}" />
</p:column>
<p:column headerText="Cost Date" >
<p:calendar value="#{dataItem.CDate}" pattern="MM/dd/yyyy" navigator="true" />
</p:column>
<p:column headerText="Delete" >
<h:commandButton value="Delete" action="#{addAccountBean.deleteFixVendor(dataItem)}" >
<f:param name="disableValidation" value="true" />
</h:commandButton>
</p:column>
<f:facet name="footer" >
<h:commandButton value="Add New Vendor" action="#{addAccountBean.addFixVendor}" >
<f:param name="disableValidation" value="true" />
</h:commandButton>
</f:facet>
</p:dataTable>
</p:panel>
</f:validateBean>
</h:form>
Bean code:
#ManagedBean
#SessionScoped
public class AddAccountBean implements Serializable {
private AccountData account = new AccountData();
public String addFixVendor() {
account.getVendors().add(new VendorData());
return "";
}
public String deleteFixVendor(VendorData vData) {
account.getVendors().remove(vData);
return "";
}
Any suggestions are appreciated.
The code posted so far looks fine. The only feasible explanation for this problem is a broken equals() method in the VendorData class. The Collection#remove() will remove the first item in the collection which equals() the given object.
Provided that your (base) entity has an id property, then this should do:
#Override
public boolean equals(Object other) {
return (other != null && getClass() == other.getClass() && id != null)
? id.equals(((VendorData) other).id)
: (other == this);
}
#Override
public int hashCode() {
return (id != null)
? (getClass().hashCode() + id.hashCode())
: super.hashCode();
}
Related
I have the following xhtml:
<h:form>
...
<p:dataTable id="table" value="#{BEAN.rows} var="row" >
<p:ajax event="rowEdit" listener="#{BEAN.rowEdit}" />
<p:ajax event="rowEditInit" listener="#{BEAN.rowEditInit}" />
<p:ajax event="rowEditCancel" listener="#{BEAN.rowEditCancel}" />
...
<p:columns id="columnsElement"
value="#{BEAN.columns} var="column" >
<f:facet name="header" >
<h:outputText value="#{column.code}" />
<p:selectBooleanCheckbox id="selectAll"
disabled="#{BEAN.rowInEdit}" />
</f:facet>
<p:cellEditor>
<f:facet name="output">
<p:selectBooleanCheckbox id="cellCheck"
disabled="#{BEAN.rowInEdit} />
</f:facet>
<f:facet name="input">
<p:selectBooleanCheckbox id="selectAll"
disabled="true" />
</f:facet>
</p:cellEditor>
</p:columns>
</p:dataTable> </h:form>
With the RowEdit listeners as follows:
public void rowEditInit(RowEditEvent event) {
rowInEdit = true;
UIData table = (UIData) event.getComponent();
String tableId = table.getClientId();
updateTableCheckboxes(tableId, table.getRowIndex()); }
public void rowEdit(RowEditEvent event) {
rowInEdit = false;
UIData table = (UIData) event.getComponent();
String tableId = table.getClientId();
updateTableCheckboxes(tableId, table.getRowIndex()); } // same for rowEditCancel
public void updateTableCheckboxes(String tableId, int index) {
int rowNumber = 0;
for (RowData row : rows) {
int columnNumber = 0;
for (ColumnData column : columns) {
if (row != index) {
String updateCheckboxId = tableId + ":" + rowNumber
+ ":columnsElement:" + columnNumber + ":cellCheck";
FacesContext.getCurrentInstance()
.getPartialViewContext().getRenderIds()
.add(updateCheckboxId);
}
columnNumber++;
}
rowNumber++;
}
columnNumber = 0;
while (columnNumber < columns.size()) {
String selectAllCheckboxId = tableId + ":columnsElement:"
+ columnNumber + ":selectAll";
FacesContext.getCurrentInstance().getPartialViewContext()
.getRenderIds().add(selectAllCheckboxId);
columnNumber++;
}}
This is dynamically populating the Ids of the check boxes in columnsElement to be updated and is working fine for rowEditInit, disabling all checkboxes as expected. When I click the tick/cross for the rowEdit or rowEditCancel event the Ids to be updated are populated exactly the same, but only the check boxes of the row which had been edited are re-enabled.
If I click on a component elsewhere on the screen or one of the re-enabled check boxes then check boxes of other rows and the selectAll check boxes are all re-enabled as well strangely.
I have tried debugging the JSF lifecycle phases as in this blog, but all stages of the lifecycle are called exactly the same for Init, Edit and Cancel.
As far as I can see I'm doing the same thing, but getting different results in the different cases. Any suggestions why this could be happening?
Thanks
I've managed to fix this using <p:remoteCommand/> outside of the datatable as follows...
<p:remoteCommand name="updateDataTable" update="table" />
and adding oncomplete="updateDataTable()" to the rowEdit and rowEditCancel ajax events.
This has given me the desired end result, but I am still curious why my initial solution didn't work or if there's a better solution.
In my JSF application, I use a rich:dataTable as follows:
<rich:dataTable id="itemTable" value="#{backingBean.itemsList}" var="i" >
<rich:column> <f:facet name="header">ItemValue</f:facet>
<h:inputText id="myId" value="#{i.value}" style="width: 30px" />
</rich:column> </rich:dataTable>
<h:commandButton id="saveB" value="Save" action="#{backingBean.doSave()}" />
Bean code of doSave:
public String doSave() {
Iterator<Item> = itemsList.iterator();
while(iter.hasNext()) {
//do something
}
}
In the doSave()-Method, i need to know the row index of the current Item, is there a way to achieve this?
While Richfaces extended data tables support a selection management, Richfaces datatables don't.
What I found the easiest way to do to retrieve a somehow selected item from a list, is to add an icon to every row. For this, put the command-Button into the data table itself:
<rich:dataTable id="itemTable" value="#{backingBean.itemsList}" var="i" >
<rich:column>
<h:inputText id="myId" value="#{i.value}" />
<h:commandButton id="saveB" action="#{backingBean.doSave}" />
</rich:column>
</rich:dataTable>
In the bean code, provide the method doSave but with an additional parameter ´ActionEvent´
public String doSave(ActionEvent ev) {
Item selectedItem = null;
UIDataTable objHtmlDataTable = retrieveDataTable((UIComponent)ev.getSource());
if (objHtmlDataTable != null) {
selectedItem = (Item) objHtmlDataTable.getRowData();
}
}
private static UIDataTable retrieveDataTable(UIComponent component) {
if (component instanceof UIDataTable) {return (UIDataTable) component;}
if (component.getParent() == null) {return null;}
return retrieveDataTable(component.getParent());
}
You see, that the ActionEvent ev provides you with the source element (UIComponent)ev.getSource(). Traverse through it until you hit the UIDataTable element and use it's row data.
Possible way two is to justgive the element as parameter with the function call:
<rich:dataTable id="itemTable" value="#{backingBean.itemsList}" var="i" >
<rich:column>
<h:inputText id="myId" value="#{i.value}" />
<h:commandButton id="saveB" action="#{backingBean.doSave(i)}" />
</rich:column>
</rich:dataTable>
and in the bean
public String doSave(Item item) {
// do stuff
}
That's not as clean, but should work with the EL, too. Hope, it helps...
I have a datatable in which first component is delete button and last is checkbox.
Now problem is that when I delete a row it reset the checkbox selected in other row
I dont want to uncheck the selected checkbox in other row.
<h:dataTable id="languageTableBody" value="#{countryBean.countryLanguageList}" var="countryLangObj" >
<h:column>
<f:facet name="header">#{msg['country.language.label.delete']}</f:facet>
<p:commandLink action="#{countryBean.deleteRow}" immediate="true" update="#form" process="#this">
<h:graphicImage value="../images/delete.png" />
<f:setPropertyActionListener target="#{countryBean.langId}" value="#{countryLangObj.language.languageCode}" />
</p:commandLink>
</h:column>
<h:column>
<f:facet name="header">#{msg['country.language.label.assignlanguage']}</f:facet>
<h:inputText readonly="true" value="#{countryLangObj.language.languageName}" id="languageName" />
</h:column>
<h:column>
<f:facet name="header">#{msg['country.language.label.languagecode']}</f:facet>
<h:inputText readonly="true" value="#{countryLangObj.language.languageCode}" />
</h:column>
<h:column>
<f:facet name="header">#{msg['country.language.label.defaultlanguage']}</f:facet>
<h:selectBooleanCheckbox id="checkBox" value="#{countryLangObj.isDefaultLanguage}" onclick="dataTableSelectOneRadio(this)" />
</h:column>
</h:dataTable>
countryBean.java
public String deleteRow(){
System.out.println("deleteRow()::Enter");
String delLangId = getLangId();
System.out.println("getLangId(): "+getLangId());
if(null != delLangId && delLangId.trim().length() > 0){
System.out.println("Delete Language Code: "+delLangId);
List<CountryLanguageDTO> countryLangList = getCountryLanguageList();
System.out.println("countryLangList: "+ (null == countryLangList));
List<CountryLanguageDTO> tempCountryLangList = new ArrayList<CountryLanguageDTO>();
for (CountryLanguageDTO countryLanguage : countryLangList) {
System.out.println("wewewewew: "+delLangId.equalsIgnoreCase(countryLanguage.getLanguage().getLanguageCode()));
if(!delLangId.equalsIgnoreCase(countryLanguage.getLanguage().getLanguageCode())){
tempCountryLangList.add(countryLanguage);
}
}
setCountryLanguageList(tempCountryLangList);
}
return SUCCESS;
}
addCountry.js
function dataTableSelectOneRadio(radio) {
var id = radio.name.substring(radio.name.lastIndexOf(':'));
var el = radio.form.elements;
for (var i = 0; i < el.length; i++) {
if (el[i].name.substring(el[i].name.lastIndexOf(':')) == id) {
el[i].checked = false;
}
}
radio.checked = true;
}
It happens because of this:
<p:commandLink action="#{countryBean.deleteRow}" immediate="true" update="#form" process="#this">
When clicking the button, you will transmit the form with the checkboxes. But because of process="#this" you will not convert/validate/store any inputs. The only thing that will get processed is the button itself. After that, because of update="#form" the whole form (including the table with the checkboxes) will be re-rendered. But with the old values.
Thus the solution is to either change process="#this" to #form or to add Ajax functionality to store the new values of each checkbox every time the value changes.
It could be a problem with bean scope. If scenario is like:
Precondition:
-Table are just showed.
Scenario:
-Select some checkbox.
-Delete row
Current result
-Selected checkbox is unchecked.
So after delete row you propably reinit view so all data get default state.
Try to change your scope to View(CDI) or Session.
I have a datatable with search field and commandLink to sort. The commandLink that I use to trigger sorting is located not in column header but on the header of datatable. When I load my page and use only commandLink to sort everything works ok. Table sorts in two orders and I see result on my page. Problem appears when I search something in globalFilter. It also works, but after that I cant sort my table. I clear inputText of globalFilter and I cant sort table. To sum up, I see result of sorting only when I not use search field. Sort operation works but request not update the datatable. I put my code below. Maybe somebody knows how to solve it.
<ui:composition>
<p:panel header="Moje pomiary" footer="#{msgs.footer}" id="myMeasurement">
<h:form id="form" prependId="false">
<p:dataTable var="m" value="#{myMeasurementTable.measurement}" id="measureList" editable="true"
widgetVar="mTable"
emptyMessage="No files found with given criteria" filteredValue="#{myMeasurementTable.filteredMeasurement}" >
<f:facet name="header">
Sortowanie według: <p:commandLink id="sortByName" actionListener="#{myMeasurementTable.sortByName}" update="measureList">
<h:outputText value="nazwa pliku" />
</p:commandLink>
|<h:commandLink action="#{myMeasurementTable.sortByArchivisationDate}"> data archiwizacji </h:commandLink>
|<h:commandLink action="#{myMeasurementTable.sortByMeasureDate}"> data badania </h:commandLink>
<p:outputPanel styleClass="searchPanel">
<h:outputText value="Szukaj: " />
<p:inputText styleClass="globalFilter" id="globalFilter" onkeyup="mTable.filter()" style="width:150px" />
</p:outputPanel>
</f:facet>
<p:column headerText="Informacje pomiarowe" style="width:125px" filterStyle="display:none" filterBy="#{m.fileName} #{m.measureDate} #{m.place} #{m.archivisationDate}"
filterMatchMode="contains" >
<p:separator styleClass="separatorColumn"/>
Nazwa pliku: <h:outputText value="#{m.fileName}" /><br />
Data badania: <h:outputText value="#{m.measureDate}" /><br />
Data archiwzacji: <h:outputText value="#{m.archivisationDate}" /><br />
Miejscowość: <h:outputText value="#{m.place}"/> <br />
Współrzędne GPS:
</p:column>
<p:column headerText="Wykresy">
<img src="/tmp/21/myfile.xls/myfile.xls_Parametr x.png" width="150"/>
</p:column> </p:dataTable></h:form></p:panel></ui:composition>
and part of my bean
#ManagedBean(name = "myMeasurementTable")
#ViewScoped
public class myMeasurementTable implements Serializable{
private static final long serialVersionUID = -9193902657201234669L;
private List<Measurement> measurement;
private List<Measurement> filteredMeasurement;
private boolean sortAscending = true;
public myMeasurementTable() {
measurement = new ArrayList<Measurement>();
fillTable(measurement);
}
public String sortByName() {
System.out.println("naciskam sortowanie");
if (sortAscending) {
Collections.sort(measurement, new Comparator<Measurement>() {
#Override
public int compare(Measurement m1, Measurement m2) {
return m1.getFileName().compareTo(m2.getFileName());
}
});
sortAscending = false;
} else {
Collections.sort(measurement, new Comparator<Measurement>() {
#Override
public int compare(Measurement m1, Measurement m2) {
System.out.println(m2.getFileName());
return m2.getFileName().compareTo(m1.getFileName());
}
});
sortAscending = true;
}
return null;
}
Ok I found solution on primefaces forum. It's simple. I only added oncomplete="mTable.filter()" to commandButton and everything works as I want.
I have a h:datatable showing a list of rows, and the fields of each row are input fields.
I render an "Add Row" button before the table, and a "Remove Row" button on each row of the table.
The baking bean is viewScoped, and the buttons add/remove elements from the java list in the backing bean, and then return to the same view.
I set the immediate attribute to "true" in the buttons in order to not validate the input fields when I add or remove a row.
Everything works ok but one thing: the values of the input fileds are cleared. I thought that the view kept the values beacuse the bean is viewScoped.
How can I achieve adding/removing rows without triggering validations and keeping the values that were already typed by the user in the form?
My view:
<h:form>
<h:commandButton value="Añadir Fila" immediate="true" action="#{tablaController.addRowAction}" />
<h:dataTable value="#{tablaController.lista}" var="fila" cellpadding="0" cellspacing="0" border="1">
<f:facet name="header">TABLA</f:facet>
<h:column>
<f:facet name="header"><h:outputLabel value="NOMBRE" /></f:facet>
<h:inputText id="nom" value="#{fila.nombre}" />
<h:message for="nom" class="msjError" />
</h:column>
<h:column>
<f:facet name="header"></f:facet>
<h:commandButton value="Quitar Fila" immediate="true" action="#{tablaController.removeRowAction(fila)}" />
</h:column>
</h:dataTable>
</h:form>
My backing bean:
#ManagedBean(name="tablaController")
#ViewScoped
public class TablaController {
private List<Fila> lista;
...
public TablaController() { }
...
#PostConstruct
public void init() {
this.lista = new ArrayList<Fila>();
for (int i=0; i<5; i++) {
Fila fila = new Fila();
fila.setNombre("");
this.lista.add(i,fila);
}
}
...
public String addRowAction () {
Fila fila = new Fila();
fila.setNombre("");
this.lista.add(fila);
return "";
}
public String removeRowAction (Fila f) {
boolean exito = this.lista.remove(f);
return "";
}
...
}
UPDATE --> MY SOLUTION:
I write here my solution if someone is interested.
The problem is that I use immediate="true" to skip validations, but this makes to skip the update_model_values too, so that the values entered by the user in the form are lost after clicking the add/remove buttons and re-redenring the page.
As I use "JSR-303 bean validation", my solution was to skip validations using the f:validateBean to enable/disable them. Depending on the button I click, if I want the validations to execute, I enable the bean validation (for example in a "submit" button), and if I want to skip them, I disable bean validation (like in the add/remove row buttons). But anyway the update_model_values always executes, so the values are not lost.
Here's the view:
<h:form>
<f:validateBean disabled="#{!empty param['disableValidation']}">
<h:commandButton value="Añadir Fila" action="#{tablaController.addRowAction}">
<f:param name="disableValidation" value="true" />
</h:commandButton>
<h:dataTable value="#{tablaController.lista}" var="fila" cellpadding="0" cellspacing="0" border="1">
<f:facet name="header">TABLA</f:facet>
<h:column>
<f:facet name="header"><h:outputLabel value="NOMBRE" /></f:facet>
<h:inputText id="nom" value="#{fila.nombre}" />
<h:message for="nom" class="msjError" />
</h:column>
<h:column>
<f:facet name="header"></f:facet>
<h:commandButton value="Quitar Fila" action="#{tablaController.removeRowAction(fila)}">
<f:param name="disableValidation" value="true" />
</h:commandButton>
</h:column>
</h:dataTable>
<h:commandButton value="Submit" action="#{tablaController.saveData}" />
</f:validateBean>
</h:form>
The backing bean:
#ManagedBean(name="tablaController")
#ViewScoped
public class TablaController {
private List<Fila> lista;
...
public TablaController() { }
...
#PostConstruct
public void init() {
this.lista = new ArrayList<Fila>();
for (int i=0; i<5; i++) {
Fila fila = new Fila();
fila.setNombre("fila "+i);
this.lista.add(i,fila);
}
}
...
public String addRowAction () {
Fila fila = new Fila();
fila.setNombre("");
this.lista.add(fila);
return "";
}
public String removeRowAction (Fila f) {
this.lista.remove(f);
return "";
}
...
public String saveData () {
...
//processes the valid data
//for example, calls to a service method to store them in a database
...
return "";
}
...
}
I set the immediate attribute to "true" in the buttons in order to not validate the input fields when I add or remove a row.
immediate="true" is the wrong tool for the job. It should be used to prioritize validation, not to enable/disable validation. The difference is rather huge as you encountered yourself.
You want to trigger validation conditionally. In case of e.g. required="true" that'd be as easy as
<h:inputText ... required="#{saveButtonPressed}" />
where #{saveButtonPressed} evaluates true when the save button is pressed. E.g. when its client ID is present in request parameter map.
In case of JSR 303 bean validation, that'd be a matter of
<f:validateBean disabled="#{not saveButtonPressed}">
<h:inputText ... />
</f:validateBean>
or with OmniFaces <o:validateBean> which allows controlling that on a per-command basis.
<h:commandButton id="add" ...>
<o:validateBean disabled="true" />
</h:commandButton>
I had exactly the same problem. In short, you can NOT use immediate for action that update data table(UIData) or facelet repeat. Short explanation:submitted values are not kept for re-display if inputs in UIData do not go through validation. Long explanation can be found here: long explanation and a related bug in Mojarra