I have a button on a page that causes my data table to refresh via an AJAX request. Something like this:
The solution is to have a conversational bean. In this case the p:dataTable refreshes the table and its entries without affecting the sort order. In the case when you have a p:commandButton on each line the table refresh works correctly too.
The conversational bean:
@Named
@Stateful
@ConversationScoped
public class ItemBean {
@Inject
private Conversation conversation;
private List<Item> items;
public List<Item> getItems() {
return this.items;
}
public void retrieve() {
// if it's an Ajax call then avoid retrieving items
if (FacesContext.getCurrentInstance().isPostback()) {
return;
}
// start a conversation
if (this.conversation.isTransient()) {
this.conversation.begin();
}
this.items = retrieveItems();
}
}
The associated page:
<f:metadata>
<f:event type="preRenderView" listener="#{itemBean.retrieve}" />
</f:metadata>
<h:form id="form">
<p:dataTable id="table" var="_item" value="#{testBean.items}">
... <!-- you can have the p:commandButton in a column too, to refresh the respective column only
<p:commandButton value="Refresh" action="#{itemBean.update(_item)}"
-->
</p:dataTable>
<p:commandButton value="Refresh" action="#{itemBean.update}" update=":form:table"/>
</h:form>
Add this PhaseListener to your application and both sorting and filtering will be kept after updating the DataTable.
public class DataTableUpdatePhaseListener implements PhaseListener {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase(PhaseEvent event) {
// Nothing to do here
}
@Override
public void beforePhase(PhaseEvent event) {
FacesContext facesContext = event.getFacesContext();
if (!facesContext.isPostback()) {
return;
}
PartialViewContext partialViewContext = facesContext.getPartialViewContext();
if (partialViewContext != null) {
Collection<String> renderIds = partialViewContext.getRenderIds();
for (String renderId : renderIds) {
UIComponent component = facesContext.getViewRoot().findComponent(renderId);
if (component instanceof DataTable) {
DataTable table = (DataTable) component;
if (!table.isLazy()) {
updateDataTable(table);
}
}
}
}
}
@Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
private void updateDataTable(DataTable table) {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext == null || table == null) {
return;
}
// Reapply filtering
if (!table.getFilters().isEmpty()) {
FilterFeature filterFeature = new FilterFeature();
filterFeature.decode(facesContext, table);
} else {
table.setFilteredValue(null);
}
// Reapply sorting
ValueExpression tableSortByVE = table.getValueExpression("sortBy");
if (tableSortByVE != null) {
String tableSortByExpression = tableSortByVE.getExpressionString();
// Loop on children, that are the columns, to find the one whose order must be applied.
for (UIComponent child : table.getChildren()) {
Column column = (Column) child;
ValueExpression columnSortByVe = column.getValueExpression("sortBy");
if (columnSortByVe != null) {
String columnSortByExpression = columnSortByVe.getExpressionString();
if (tableSortByExpression != null && tableSortByExpression.equals(columnSortByExpression)) {
// Now sort table content
SortFeature sortFeature = new SortFeature();
sortFeature.sort(facesContext, table, tableSortByVE,
SortOrder.valueOf(table.getSortOrder().toUpperCase(Locale.ENGLISH)),
table.getSortFunction());
break;
}
}
}
}
}
}
This is for non-lazy data tables. Data tables using a lazy model do not require this, as the lazy model will take care of sorting and filtering. For non-lazy data tables, this should work with both single and multiple column sorting but there is a bug in Primefaces that makes DataTable loose its MultiSortMeta between postbacks when updating the table. This means that the columns selected for sorting before postback are completely lost from FacesContext and component state and cannot be retrieved anymore upon postback. I'll look more into this later and provide an update if I manage to find a workaround or maybe Primefaces will fix it soon.
I implement a Comparator with my toString() method
municipios = municipioFacade.findAll();
Collections.sort(municipios, new DefaultComparator());
UIComponent table = FacesContext.getCurrentInstance().getViewRoot().findComponent(":municipios:municipios-tabla");
table.setValueExpression("sortBy", null);
comparator
public class DefaultComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
return o1.toString().compareToIgnoreCase(o2.toString());
}
}
@Rares Oltean's approach could also be implemented using preRenderView event listener. On your jsf page register listener:
<f:event listener="#{managedBean.preRenderViewListener}" type="preRenderView" />
and implement it in a ManagedBean:
public void preRenderViewListener() {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (!facesContext.isPostback()) {
return;
}
PartialViewContext partialViewContext = facesContext.getPartialViewContext();
if (partialViewContext != null) {
Collection<String> renderIds = partialViewContext.getRenderIds();
for (String renderId : renderIds) {
UIComponent component = facesContext.getViewRoot().findComponent(renderId);
if (component instanceof DataTable) {
DataTable table = (DataTable) component;
if (!table.isLazy()) {
updateDataTable(table);
}
}
}
}
}
private void updateDataTable(DataTable table) {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext == null || table == null) {
return;
}
if (!table.getFilters().isEmpty()) {
FilterFeature filterFeature = new FilterFeature();
filterFeature.decode(facesContext, table);
} else {
table.setFilteredValue(null);
}
ValueExpression tableSortByVE = table.getValueExpression("sortBy");
if (tableSortByVE != null) {
String tableSortByExpression = tableSortByVE.getExpressionString();
for (UIComponent child : table.getChildren()) {
Column column = (Column) child;
ValueExpression columnSortByVe = column.getValueExpression("sortBy");
if (columnSortByVe != null) {
String columnSortByExpression = columnSortByVe.getExpressionString();
if (tableSortByExpression != null && tableSortByExpression.equals(columnSortByExpression)) {
SortFeature sortFeature = new SortFeature();
sortFeature.sort(facesContext, table, tableSortByVE, table.getVar(),
SortOrder.valueOf(table.getSortOrder().toUpperCase(Locale.ENGLISH)),
table.getSortFunction());
break;
}
}
}
}
}
EDIT:
Solution posted below (LazyTable) works for the p:dataTable backed with LazyDataModel. Overriden load method is called after every update/refresh on the desired table and it handles sort properly. The problem with simple p:dataTable is that it performs predefined sort only on the first load, or after the click on sort column. This is a normal behaviour of a simple table.
So what are your possibilities for simple table :
Don't sort the table after update, but remove the sort column so end user is not missinformed. Add this to your action listener or action method for your update button :
UIComponent table = FacesContext.getCurrentInstance().getViewRoot().findComponent(":dataTablesForm:dataTableId");
table.setValueExpression("sortBy", null);
Update the sort of the table manually by script. This is not the best solution, but primefaces doesn't provide any client side function for "resorting" the table. Basically you know that only one column at a time can be sorted and this column has a .ui-state-active. You can use it in a script and trigger 2 clicks on that column (1. click - other sort order, 2. click - back to current sort order).
<h:form id="mainForm">
<div id="tableDiv">
<p:dataTable id="dataTable" var="item" value="#{testBean.dummyItems}">
.
.
.
</p:dataTable>
<p:commandButton value="Refresh" oncomplete="refreshSort();" update=":mainForm:dataTable"/>
</div>
And script function :
function refreshSort(){
jQuery('#tableDiv').find('.ui-state-active').trigger('click');
jQuery('#tableDiv').find('.ui-state-active').trigger('click');
}
I know this is not the best workaround, but it works. You can use it as an inspiration to make something better.
IMHO the most proper way is to update directly the component you want. So for example :
<h:form id="dataTableForm">
<p:dataTable id="dataTableToUpdate">
.
.
.
</p:dataTable>
<p:commandButton value="Refresh" update=":dataTableForm:dataTableToUpdate" />
</h:form>
It should work fine in this scenario (I suppose it is your purpose) : Open the .xhtml with your p:dataTable, sort some column (keep the facelet opened), update dataTables data from another .xhtml for example, click on refresh button. The dataTable should show your added value in correct (previously chosen) sort order - sorting was performed after update.
Hope it helped !
I had the same problem as you. I was able to fix the issue using a LazyDataModel. Because of the PrimeFaces issue with the row index, I needed to add the extra UtilitiesLazyDataModel(see row index comments):
public abstract class UtilitiesLazyDataModel <T> extends LazyDataModel<T>{
public UtilitiesLazyDataModel() {
}
@Override
public void setRowIndex(int rowIndex) {
/*
* The following is in ancestor (LazyDataModel):
* this.rowIndex = rowIndex == -1 ? rowIndex : (rowIndex % pageSize);
*/
if (rowIndex == -1 || getPageSize() == 0) {
super.setRowIndex(-1);
} else {
super.setRowIndex(rowIndex % getPageSize());
}
}
}
Then use your LazyDataModel
class with this:
public class MyDataModel extends UtilitiesLazyDataModel<MyObjectClass>{
//override getRowData and getRowKey
}