3
votes

I have a simple Databean like this:

@Model
Class DataBean{
    private List<Elements> elements;

    @PostConstruct
    private void loadElements(){
        //fetch data from database.
    }
}

im using Primefaces datatable to display the data, like this:

<h:form>
<p:dataTable 
      value="#{dataBean.elements}"
      var="element" >

      <p:column sortBy="#{element.id}"
           sortFunction="#{sortingHelper.sortNumericCallback}">
           <f:facet name="header">ID</f:facet>
           <p:commandLink action="#{pageController.navigateToDetailView(element)}"
                 value="#{element.id}">
           </p:commandLink>
      </p:column>
 </p:datatable>
 </h:form>

pageController.navigateToDetailView(element) simple sets the selected element on the next page's databean, so the detailView has its data prepared, and then returns the detail-Navigation-Outcome.

Now: The Problem: If i click on one of the commandLinks without any sorting, all is fine. If i sort by id and click on the details-link, the following is happening:

  • Request Started
  • Databean loaded (postconstruct) (sorting gone)

Now - in the second request - the page is rebuild again (in order to fire the navigateToDetailView-Action) And the datatable "knows", that i clicked on row 5. But without sorting it again, row 5 is now a different entry, since the bean gets reconstructed.

Console Output for different Points.

First I click on the page showing the Datatable. the "."'s are one comparision of my custom sort function, just to indicate the collection is sorted.

13:47:56,046 INFO  [stdout] (http--0.0.0.0-8090-1) -- Started Request --
13:47:56,047 INFO  [stdout] (http--0.0.0.0-8090-1) ---- Started RESTORE_VIEW 1 ----
13:47:56,048 INFO  [stdout] (http--0.0.0.0-8090-1) ---- Started RENDER_RESPONSE 6 ----
13:47:56,087 INFO  [stdout] (http--0.0.0.0-8090-1) PostConstruct DataBean
13:47:56,566 INFO  [stdout] (http--0.0.0.0-8090-1) -- Finished Request --

That's fine. Now im sorting by clicking the id header

13:48:15,008 INFO  [stdout] (http--0.0.0.0-8090-2) -- Started Request --
13:48:15,009 INFO  [stdout] (http--0.0.0.0-8090-2) ---- Started RESTORE_VIEW 1 ----
13:48:15,051 INFO  [stdout] (http--0.0.0.0-8090-2) ---- Started APPLY_REQUEST_VALUES 2 ----
13:48:15,052 INFO  [stdout] (http--0.0.0.0-8090-2) PostConstruct DataBean
13:48:15,124 INFO  [stdout] (http--0.0.0.0-8090-2) ..............................................................
13:48:15,124 INFO  [stdout] (http--0.0.0.0-8090-2) ---- Started PROCESS_VALIDATIONS 3 ----
13:48:15,126 INFO  [stdout] (http--0.0.0.0-8090-2) ---- Started UPDATE_MODEL_VALUES 4 ----
13:48:15,127 INFO  [stdout] (http--0.0.0.0-8090-2) ---- Started INVOKE_APPLICATION 5 ----
13:48:15,127 INFO  [stdout] (http--0.0.0.0-8090-2) ---- Started RENDER_RESPONSE 6 ----
13:48:15,387 INFO  [stdout] (http--0.0.0.0-8090-2) -- Finished Request --

That's fine, too. The Table is now sorted as it should be. Now i'm clicking on the 10th row to pick the item with the id 53;

13:48:28,295 INFO  [stdout] (http--0.0.0.0-8090-4) -- Started Request --
13:48:28,296 INFO  [stdout] (http--0.0.0.0-8090-4) ---- Started RESTORE_VIEW 1 ----
13:48:28,361 INFO  [stdout] (http--0.0.0.0-8090-4) ---- Started APPLY_REQUEST_VALUES 2 ----
13:48:28,363 INFO  [stdout] (http--0.0.0.0-8090-4) PostConstruct DataBean
13:48:28,487 INFO  [stdout] (http--0.0.0.0-8090-4) ---- Started PROCESS_VALIDATIONS 3 ----
13:48:28,501 INFO  [stdout] (http--0.0.0.0-8090-4) ---- Started UPDATE_MODEL_VALUES 4 ----
13:48:28,514 INFO  [stdout] (http--0.0.0.0-8090-4) ---- Started INVOKE_APPLICATION 5 ----
13:48:28,514 INFO  [stdout] (http--0.0.0.0-8090-4) navigateToDetail() called 
13:48:28,516 INFO  [stdout] (http--0.0.0.0-8090-4) Constructing ElementEditDataBean
13:48:28,517 INFO  [stdout] (http--0.0.0.0-8090-4) Setting ActiveElement to 42
13:48:28,518 INFO  [stdout] (http--0.0.0.0-8090-4) ---- Started RENDER_RESPONSE 6 ----
13:48:28,748 INFO  [stdout] (http--0.0.0.0-8090-4) -- Finished Request --

Note, that AFTER PostConstruct DataBean no sorting is done. (I assume since i use a form in the table, the datatable is not aware, that the sorting might have changed.)

As a result, element with id 42 is passed. (Element 42 is in the 10th position for an unsorted case)

As a result, navigateToDetailView(element) is now fired with another elementthan expected...

The Problem is ofc. that the sorted collection gets reset by the postconstruct method. I also know, that it can be solved with Conversation scope.

But I wonder if there isnt a stateless way of doing this? (I don't want to launch conversations for every sorting / page2page navigation)

Any Ideas?

Edit 1: SortingHelper is a own class, just looking like this:

 @Named
 public class SortingHelper {

/**
 * Sorts two integers correctly.
 * @param o1 integer 1
 * @param o2 integer 2
 * @return negative value if o1 is less, 0 if equal, or positive value if greater
 */
public int sortNumericCallback(Object o1, Object o2) {
    System.out.print(".");
    int i1 = Integer.parseInt((String) o1);
    int i2 = Integer.parseInt((String) o2);
    return (i1 == i2) ? 0 : (i1 > i2) ? 1 : -1;
}
 }

(Primefaces Datatable fails on sorting integers, or lets say it sorts numbers lexicographic: 11 < 5 etc.)

But even if i do not mind about the sorting and using NO custom sort function the outcome is the same.

1
is sortingHelper in sortFunction="#{sortingHelper.sortNumericCallback}" a class defined by you? if it is, show the code - ClydeFrog
do you experience the same sorting problem if you do the output <h:outputText value="#{element.id}" /> instead of <p:commandLink action="#{pageController.navigateToDetailView(element)}" value="#{element.id}"></p:commandLink> ? - ClydeFrog
@ClydeFrog added. The sorting is right. It is just, that its dropped, when I click the link to the element, because of the @PostConstruct dataloading. And in the second rendering of the page (to call the navigation function) the datatable assumes, that the List is sorted, while it is no longer. So it 'knows' that i wanted to retrieve the element from the 5th position - but this element has changed then. - dognose
Maybe this earlier thread can assist? or put the <h:form> tag around the <p:commandLink> and not the entire <p:dataTable> - ClydeFrog
@ClydeFrog Neither Nor... :( - dognose

1 Answers

0
votes

As of the comments, I now modified the Databean to start a conversation after loading.

@ConversationScoped
Class DataBean{
    private List<Elements> elements;

    @Inject
    private Conversation conversation;

    @PostConstruct
    private void loadElements(){
        if (this.conversation.isTransient()) 
             this.conversation.begin();

        //fetch data from database.
    }
}

in the navigation functions of my pageController i stop that conversation again :

public String navigateToDetailView(Element element) {
    //pass element to next databean.
    conversation.end();
    //...
    return "detailView";
}

This however caused the problem, that navigating back with the browser leads to invalid conversations (they are ended).

To solve this, i created a custom filter that basically disables the caching for the browsers, so they refresh the page on history.back() and therefore having a brandnew, valid conversation id.

The Filter looks like this:

public class NoCacheFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse hsr = (HttpServletResponse) res;
        hsr.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
        hsr.setHeader("Pragma", "no-cache"); // HTTP 1.0.
        hsr.setDateHeader("Expires", 0); // Proxies.
        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub  
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
    }
}

with this part in web.xml:

<filter>
        <filter-name>noCacheFilter</filter-name>
        <filter-class>com.example.NoCacheFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>noCacheFilter</filter-name>
        <url-pattern>*.xhtml</url-pattern>
    </filter-mapping>

Now i just need to figure out, how to end conversations if the user chooses no navigate away from the side, instead of calling the showDetail()-Action.