Reputation: 678
I have a button on a page that causes my data table to refresh via an AJAX request. Something like this:
<h:form id="datatable">
<p:dataTable/>
</h:form>
<p:commandButton update=":datatable">
This is all fine an dandy except that when the table is refreshed it reverts to not sorting anything while still showing that it's sorting based on the previous value. In other words, the header is still highlighted and the arrow is still pointing in the sort direction but no sort is actually being performed. Obviously this isn't ideal.
Ideally I'd like the component to keep it's sort order in the view state and then submit the proper parameters during the AJAX request (so that the sort is correctly defined). Am I missing a parameter or something? Does anyone else have this issue?
From what I can tell when the table is expecting a sort back it posts the following options:
<componentID>_sortDir
<componentID>_sortKey
<componentID>_sorting
<componentID>_updateBody
When I refresh the form this doesn't happen. It also doesn't happen if I just refresh the table (thought I could work around things by updating the component directly). Is there a way to get the table to refresh correctly?
Upvotes: 10
Views: 25219
Reputation: 29
Although I am not using prime faces, I faced similar issue of restoring the column sort once the page data gets refreshed. My page was getting refreshed with new data every minute and I did not wanted the user to click on column to sort every time.
Setting stateSave: true only did not work in chrome browser. So I had to use stateSaveCallback and stateLoadCallback methods along with stateSave: true. Please refer to below link for further details.
https://datatables.net/reference/option/stateSave
Upvotes: -1
Reputation: 1982
@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;
}
}
}
}
}
Upvotes: 0
Reputation: 634
I wrote an extension to @truemmer's solution. His reverts the sorting order back to the default, where as mine will reverts to the previous sort the user selected.
function postAjaxSortTable(datatable)
{
var selectedColumn = datatable.jq.find('.ui-state-active');
if(selectedColumn.length <= 0)
{
return;
}
var sortorder = "ASCENDING";
if(selectedColumn.find('.ui-icon-triangle-1-s').length > 0)
{
sortorder = "DESCENDING";
}
datatable.sort(selectedColumn, sortorder);
}
Updating the same table as truemmer's works like this:
<p:commandButton value="refresh" action="#{tableController.refreshPrices}" update="myTable" oncomplete="postAjaxSortTable(myTableWidget)" />
EDIT: Primefaces 4.0 MultiSort support
function postAjaxSortTable(datatable) {
var selectedColumn = undefined;
// multisort support
if(datatable && datatable.cfg.multiSort) {
if(datatable.sortMeta.length > 0) {
var lastSort = datatable.sortMeta[datatable.sortMeta.length-1];
selectedColumn = $(document.getElementById(lastSort.col));
}
} else {
selectedColumn = datatable.jq.find('.ui-state-active');
}
// no sorting selected -> quit
if(!selectedColumn || selectedColumn.length <= 0) {
return;
}
var sortorder = selectedColumn.data('sortorder')||"DESCENDING";
datatable.sort(selectedColumn, sortorder, datatable.cfg.multiSort);
}
Upvotes: 10
Reputation: 666
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.
Upvotes: 1
Reputation: 4529
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
}
Upvotes: 0
Reputation: 2099
First of all, there is an open ticket on the PrimeFaces Issue Tracker, which targets this question but was recently labeled as "won't fix".
Nevertheless, I found a nice workaround. The sorting of PrimeFaces tables can be triggered by calling an undocumented JavaScript method on the datatable's widget. The following might not work for future releases of PrimeFaces, but it does for 3.4.2:
Just add the following to your component, which triggers the update:
oncomplete="myTableWidget.sort($(PrimeFaces.escapeClientId('#{p:component('sortColumnId')}')), 'ASCENDING')"
The table might look like this:
<p:dataTable id="myTable"
widgetVar="myTableWidget"
value="#{myArticles}"
var="article"
sortBy="#{article.price}"
sortOrder="ASCENDING">
...
<p:column id="price" sortBy="#{article.price}">
<f:facet name="header">
<h:outputText value="Price" />
</f:facet>
<h:outputText value="#{article.price}" />
</p:column>
</p:dataTable>
So updating the table could work like this.
<p:commandButton value="refresh" action="#{tableController.refreshPrices}" update="myTable" oncomplete="myTableWidget.sort($(PrimeFaces.escapeClientId('#{p:component('price')}')), 'ASCENDING')" />
Upvotes: 5
Reputation: 181
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>
Upvotes: 0
Reputation: 45
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());
}
}
Upvotes: 0
Reputation: 2427
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 !
Upvotes: 3