8

I just enabled multiple sorting in the showcase code for "DataTable - Lazy Loading"

datatableLazy.xhtml

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>CarDataTable</title>
</h:head>
<h:body>
<h:form id="form">
    <p:dataTable var="car" value="#{tableBean.lazyModel}" paginator="true"
        rows="10"
        paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}"
        rowsPerPageTemplate="5,10,15" id="carTable" lazy="true"
        sortMode="multiple">

        <p:ajax event="rowSelect" listener="#{tableBean.onRowSelect}"
            update=":form:display" oncomplete="carDialog.show()" />

        <p:column headerText="Model" sortBy="#{car.model}"
            filterBy="#{car.model}">
            <h:outputText value="#{car.model}" />
        </p:column>

        <p:column headerText="Year" sortBy="#{car.year}"
            filterBy="#{car.year}">
            <h:outputText value="#{car.year}" />
        </p:column>

        <p:column headerText="Manufacturer" sortBy="#{car.manufacturer}"
            filterBy="#{car.manufacturer}">
            <h:outputText value="#{car.manufacturer}" />
        </p:column>

        <p:column headerText="Color" sortBy="#{car.color}"
            filterBy="#{car.color}">
            <h:outputText value="#{car.color}" />
        </p:column>
    </p:dataTable>
</h:form>
</h:body>
</html>

TableBean.java

package com.solartis.primefaces.sample;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.faces.bean.ManagedBean;

import org.primefaces.model.LazyDataModel;

@ManagedBean
public class TableBean {

private LazyDataModel<Car> lazyModel;

private Car selectedCar;

private List<Car> cars = new ArrayList<Car>();

private final static String[] colors;

private final static String[] manufacturers;

static {
    colors = new String[10];
    colors[0] = "Black";
    colors[1] = "White";
    colors[2] = "Green";
    colors[3] = "Red";
    colors[4] = "Blue";
    colors[5] = "Orange";
    colors[6] = "Silver";
    colors[7] = "Yellow";
    colors[8] = "Brown";
    colors[9] = "Maroon";

    manufacturers = new String[10];
    manufacturers[0] = "Mercedes";
    manufacturers[1] = "BMW";
    manufacturers[2] = "Volvo";
    manufacturers[3] = "Audi";
    manufacturers[4] = "Renault";
    manufacturers[5] = "Opel";
    manufacturers[6] = "Volkswagen";
    manufacturers[7] = "Chrysler";
    manufacturers[8] = "Ferrari";
    manufacturers[9] = "Ford";
}

public TableBean() {
    populateRandomCars(cars, 50);
    lazyModel = new LazyCarDataModel(cars);
}

public Car getSelectedCar() {
    return selectedCar;
}

public void setSelectedCar(Car selectedCar) {
    this.selectedCar = selectedCar;
}

public LazyDataModel<Car> getLazyModel() {
    return lazyModel;
}

private void populateRandomCars(List<Car> list, int size) {
    for (int i = 0; i < size; i++) {
        list.add(new Car(getRandomModel(), getRandomYear(),
                getRandomManufacturer(), getRandomColor()));
    }
}

private String getRandomColor() {
    return colors[(int) (Math.random() * 10)];
}

private String getRandomManufacturer() {
    return manufacturers[(int) (Math.random() * 10)];
}

private int getRandomYear() {
    return (int) (Math.random() * 50 + 1960);
}

private String getRandomModel() {
    return UUID.randomUUID().toString().substring(0, 8);
}
}

LazyCarDataModel.java

package com.solartis.primefaces.sample;

import java.util.ArrayList;

/**
 * Dummy implementation of LazyDataModel that uses a list to mimic a real 
   datasource like a database.
 */
public class LazyCarDataModel extends LazyDataModel<Car> {

private List<Car> datasource;

public LazyCarDataModel(List<Car> datasource) {
    this.datasource = datasource;
}

@Override
public Car getRowData(String rowKey) {
    for(Car car : datasource) {
        if(car.getModel().equals(rowKey))
            return car;
    }

    return null;
}

@Override
public void setRowIndex(int rowIndex) {

    if (rowIndex == -1 || getPageSize() == 0) {
        super.setRowIndex(-1);
    } else
        super.setRowIndex(rowIndex % getPageSize());
}

@Override
public Object getRowKey(Car car) {
    return car.getModel();
}

@Override
public List<Car> load(int first, int pageSize, 
                      List<SortMeta> multiSortMeta,Map<String, String> filters) {

    System.out.println("\nTHE INPUT PARAMETER VALUE OF LOAD METHOD :   
    \t"+"first=" + first + ", pagesize=" + pageSize + ", multiSortMeta=" + 
    multiSortMeta + " filter:" + filters);

    System.out.println("\nTHE MULTISORTMETA CONTENT  : \t");

    if (multiSortMeta != null) {
        for (SortMeta sortMeta : multiSortMeta) {
            System.out.println("SORTFIELD:" +sortMeta.getSortField());
            System.out.println("SORTORDER:" +sortMeta.getSortOrder());
                    System.out.println("SORTFUNCTION:"
                                                         +sortMeta.getSortFunction());
            System.out.println("COLUMN:" +sortMeta.getColumn());
            System.out.println("CLASS:" +sortMeta.getClass());
        }
    }

    List<Car> data = new ArrayList<Car>();

    //filter
    for(Car car : datasource) {
        boolean match = true;

        for(Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
            try {
                String filterProperty = it.next();
                String filterValue = filters.get(filterProperty);
                String fieldValue =  String.valueOf(car.getClass().
                                getField(filterProperty).get(car));

                if(filterValue == null || fieldValue.startsWith(filterValue)) {
                    match = true;
                }
                else {
                    match = false;
                    break;
                }
            } catch(Exception e) {
                match = false;
            } 
        }

        if(match) {
            data.add(car);
        }
    }


    //rowCount
    int dataSize = data.size();
    this.setRowCount(dataSize);

    //paginate
    if(dataSize > pageSize) {
        try {
            return data.subList(first, first + pageSize);
        }
        catch(IndexOutOfBoundsException e) {
            return data.subList(first, first + (dataSize % pageSize));
        }
    }
    else {
        return data;
    }
}
}

It works well except when I paginate with multiple columns sorting, the load() method with List<SortMeta> does not give me the column details which are currently sorted to carry over to the other page, unlike the load() method with String sortField, SortOrder sortOrder which gives those sorting details.

For example:

  1. Click on the sorting arrow in "manufacturer" and then Ctrl+click on the sorting arrow of "year"

    • you would get the sorting column details to the load() method (I have printed the input parameters value inside load method).
  2. Now, do pagination. Here the load() method fails to give the sorting columns detail

    • not only for pagination, if you enter column filter values after clicking on the sorting columns, the same problem exist

How can I fix this?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
senthil_sss
  • 193
  • 2
  • 3
  • 13

2 Answers2

2

I solved this question in a temporary way... Have a sessionscoped managed bean for storing the sorting column details, inorder to get within load() during pagination, like:-

@ManagedBean
@SessionScoped

public class StoreSortColumnDetail implements Serializable{


    /** holds multisort values**/
private List<SortMeta> mMultiSortMeta;


public List<SortMeta> getMultiSortMeta() {
    return mMultiSortMeta;
}

public void setMultiSortMeta(List<SortMeta> multiSortMeta) {
    mMultiSortMeta = multiSortMeta;
}

public void clearMultiSortMeta() {
    if(this.mMultiSortMeta != null)
        this.mMultiSortMeta.clear();

}
}

and use it in load() as like this:

@Override
public List<Car> load(int first, int pageSize, 
                  List<SortMeta> multiSortMeta,Map<String, String> filters) {

/** Instance to the SessionScoped scoped StoreSortColumnDetail managed bean*/
@ManagedProperty(value="#{StoreSortColumnDetail }")
private StoreSortColumnDetail storeSortColumnDetail ;

public void setStoreSortColumnDetail (StoreSortColumnDetail sortColumnDetail ) {
    this.storeSortColumnDetail = sortColumnDetail ;
}

/** to hold the handled sort column detail**/
List<SortMeta> handledMultiSortMeta = new ArrayList<SortMeta>();

/*Here starts the multisortmeta handling process*/
   /** check for List<SortMeta> for null**/
if(multiSortMeta != null ) {

    /** updates StoreSortColumnDetail's List<SortMeta> with Load()'s List<SortMeta>**/
    storeSortColumnDetail.setMultiSortMeta(multiSortMeta);
    handledMultiSortMeta  = multiSortMeta;
} 
/** check for List<SortMeta> for notnull **/
else if (multiSortMeta == null) {

    /**assigns Load()'s List<SortMeta> with StoreSortColumnDetail's List<SortMeta>**/
    handledMultiSortMeta  = storeSortColumnDetail.getMultiSortMeta();
} 


   /*Now u have handled multisortmeta from load()...
     and u can process now from handledMultiSortMeta*/
}

i hope u came to know how i handled, if not intimate me... but this is a temporary way,need to handle it through primefaces way...

senthil_sss
  • 193
  • 2
  • 3
  • 13
  • @Tiny: plz..take a look at my answer and give ur comments – senthil_sss Jun 12 '13 at 05:28
  • Yeah! Thank you for answering this question. I will see this later in a few days, since I'm currently a bit busy with other stuff for a few days. Please keep it up. Don't delete it. – Tiny Jun 13 '13 at 18:40
  • Yes, I gave it a try and it worked great. Nevertheless it is still incomplete to meet my requirements because `sortColumn` should be retained (with column highlight and sort derection) on the client-side even after `dataTable` is rendered i.e when `` is pressed to add a row into the database and consequently into `dataTable` which doesn't happen util some JavaScript magic is used. I tried the answers to [this](http://stackoverflow.com/q/10032356/1391249) question but unfortunately none of them worked for me as expected. Did you do that in your application? Thanks so much. – Tiny Jun 17 '13 at 06:50
  • @Tiny: unfortunately i didn't take up ur scenario.I will try urs and inform u soon – senthil_sss Jun 17 '13 at 10:31
  • @Tiny: can u get me something to this unrelated issue : http://stackoverflow.com/questions/17145023/assign-value-expression-inplace-of-method-expression-jsf – senthil_sss Jun 17 '13 at 10:31
  • I've looked into your [previous](http://stackoverflow.com/q/17145023/1391249) question. Until now, I didn't use `` and `` though I gave it a try after reading some tutorials but it didn't work for me. It requires me to spend sometime on it. Therefore, I'm right now unable to see the cause of that exception, sorry. Nevertheless, I'll keep trying though, hoping you'll get answer(s) before that. (I'm currently more comfortable in Spring than JSF). – Tiny Jun 19 '13 at 04:23
  • @Tiny : ok..no probs..i'll reply u soon,regarding ur scenario!!. – senthil_sss Jun 19 '13 at 04:30
  • Back to my previous comment : if it doesn't work reasonably, then I want to start a bounty on your question to see, if someone has a reliable solution or not. If not, then I want to leave Primefaces forever because in addition to this issue in the question, I found so many other issues in this framework (with the status "WontFix" in the next release or so) and many of them are likely to be encountered on its way. My application is pending for months because of these issues. Thank you very much for your help, sir. – Tiny Jun 19 '13 at 04:33
0

While this approach might do the trick, you can easily delegate the actual operation to Primefaces in your lazyDataModel. By doing that you keep your codebase clearer since you won't have any other classes to operate and you will reuse the already developed components(which you should anyhow).

If you examined the PrimeFaces source code you will see that DataTable uses concrete classes for each of its features such as filtering, sorting, expanding etc... For sorting Primefaces uses a class called BeanPropertyComparator this class requires certain properties to its constructor but most of these attributes can be get from sortMeta attribute sent to the load method of lazyDataModel. But if you want to get all the attributes you need to have the DataTable object which you can get from FacesContext if you know the client side id of the DataTable in question.

Suppose you have the datatable instance in a variable called activeTable than all you have to do is this:

UIColumn sortColumn = sortMeta.getSortColumn()
if(sortColumn.isDynamic())
  ((DynamicColumn)sortColumn).applyStatelessModel();
//for single mode sorting
ValueExpression sortByVal = activeTable.getValueExpression("sortBy");
//multiColumn sorting 
ValueExpression sortByVal = sortColumn.getValueExpression("sortBy");
int caseSensitive = activeTable.isCaseSensitiveSort();
SortOrder order = sortMeta.getSortOrder();
MethodExpression exp = sortColumn.getSortFunction();
//pass required properties to constructor
//single mode sort
Collections.sort(filteredItems, new BeanPropertyComparator(......))
//for multi column sort use ChainedBeanPropertyComparator and add every new comparator to it than
Collections.sort(filteredItems, chainedComparator);

that way by default your sort will support any feature that primefaces default mechanism support without sacrificing safety.

YamYamm
  • 381
  • 1
  • 3
  • 12
  • 1
    If you override core PF classes, it is good to also state which version this was for! (it is good to mention version info at all times as a matter of fact) – Kukeltje Oct 20 '15 at 13:52
  • you are correct, i believe this solution is good for any primefaces version above 5.0, also overriding is not mandatory here, i meant directly using concrete BeanPropertyComparator class by instantiating a new object. – YamYamm Oct 20 '15 at 20:07