LazyDataModel, custom filter with search button

23 views Asked by At

1)I need to perform a filtered search using a custom filter object that contains the query parameters supported by my API. I have devised a solution that works, but I'm not sure if it's the best way to implement it.

Let's assume that my API accepts 10 different query parameters, while my ContractDto only has a few fields.

In this case, I can't use the default filterBy implemented by DataTable, so I need to perform some actions inside my load method.

2)I need to be able to reset the page value to 0 when clicking on the search button so that I can perform the correct query.

I have come up with two solutions, but I don't like either of them very much.

Question 1

My view

@Component
@ViewScoped
public class Contracts {
    @Autowired
    private Service service;

    private LazyDataModel<ContractDto> lazyModel;
    
    private ContractFilter filter = new ContractFilter();
    
    private Selections selections;
    
    @PostConstruct
    public void init() {
        selections = service.loadSelections();
        search();
    }

    public void search() {
        lazyModel = new LazyContractDto(service, filter);
    }

    //GETTERS AND SETTERS

}

LazyDataModel

@SuppressWarnings("serial")
public class LazyContractDto extends LazyDataModel<ContractDto> {

    private Service service;

    private ContractFilter filter;

    Map<String, String> filterMap;

    public LazyContractDto(Service service, ContractFilter filter) {
        this.service = service;
        this.filter = filter;
    }

    @Override
    public int count(Map<String, FilterMeta> filterBy) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public List<ContractDto> load(int first, int pageSize, Map<String, SortMeta> sortBy,
            Map<String, FilterMeta> filterBy) {
        Optional<Pageable> optionalPageable = createPageable(first, pageSize, sortBy);
        LazyResponseHolder<ContractDto> lazyResponseHolder = service.findAllContracts(optionalPageable);
        this.setRowCount(lazyResponseHolder.getRowCount() != null ? lazyResponseHolder.getRowCount() : 0);
        return lazyResponseHolder.getLazyList();
    }

    private Optional<Pageable> createPageable(int first, int pageSize, Map<String, SortMeta> sortBy) {
    
        filterMap = contractFilter.createFilterMap();

        Pageable pageable = new Pageable();
        pageable.setPage(first / pageSize);
        pageable.setSize(pageSize);

        // Return di optional empty 
        if (sortBy.isEmpty() && filterMap.isEmpty()) {
            return Optional.of(pageable);
        }

        // add filters to custom Pageable object
        filterMap.forEach(pageable::addFilterItem);

        if (sortBy != null) {
            sortBy.entrySet().stream().map(entry -> {
                SortMeta sortMeta = entry.getValue();
                String order = sortMeta.getOrder() == SortOrder.ASCENDING ? "asc" : "desc";
                return sortMeta.getField() + "," + order;
            }).forEach(sortString -> pageable.addSortItem(sortString));
        }
        return Optional.of(pageable);
    }
}

Filter

public class ContrattoFilter {
    private String id;
    private String state;
    private String revision;
    more...

    public Map<String, String> createFilterMap() {
        Map<String, String> filterMap = new HashMap<>();
        if (id != null && !id.isBlank()) {
            filterMap.put("id", id);
        }
        if (stato != null && !stato.isBlank()) {
            filterMap.put("state", state);
        }
        if (revisione != null && !revisione.isBlank()) {
            filterMap.put("revision", revision);
        }
        // add more if needed..
        return filterMap;
    }

    //getters and setters

}

xhtml

<h:form id="filterForm">
    <ui:param name="filter" value="#{contratti.filter}" />
    <div class="ui-fluid formgrid grid">
        <div class="field col-12 md:col-1">
            <p:outputLabel for="@next" value="ID" />
            <p:inputText id="id" value="#{filter.id}"
                placeholder="CTR00" styleClass="uppercase">
            </p:inputText>
        </div>
        <div class="field col-12 md:col-1">
            <p:outputLabel for="@next" value="STATE" />
            <p:inputText id="state" value="#{filter.state}" placeholder="ID"
                styleClass="uppercase">
            </p:inputText>
        </div>
        <div class="field col-12 md:col-1">
                <p:outputLabel for="@next" value="REVISION" />
                <p:selectOneMenu id="revision" value="#{filter.revision}"
                    styleClass="uppercase" panelStyleClass="uppercase">
                    <f:selectItem itemLabel="--" itemValue="#{null}" />
                    <f:selectItems
                        value="#{contracts.selections.revisionList}"
                        var="item" itemLabel="#{item.description}"
                        itemValue="#{item.description}" />
                </p:selectOneMenu>
            </div>
        ...more fields
        <div class="field col-12 md:col-1">
            <p:outputLabel for="@next" value="&#160;" />
            <p:commandButton value="Search" icon="pi pi-search"
                styleClass="ui-button-success" action="#{contracts.search()}"
                process="filterForm" update=":contracts:contractsTable" />
        </div>
    </div>
</h:form>
<br />
<h:form id="contracts">
    <p:dataTable id="contractsTable" var="item"
        value="#{contracts.lazyModel}" lazy="true" size="small" widgetVar="contractsTable"
        paginator="true" rows="10" allowUnsorting="true"
        paginatorPosition="top"
        paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
        currentPageReportTemplate="{startRecord}-{endRecord} of {totalRecords} results"
        rowsPerPageTemplate="2,5,10,20" showGridlines="true">

        
        <p:column headerText="value 1" sortBy="#{item.value1}">
            <h:outputText value="#{item.value1}" />
        </p:column>
        <p:column headerText="value2" sortBy="#{item.value2}">
            <h:outputText value="#{item.value1}" />
        </p:column>
    </p:dataTable>
</h:form>

Question 2

solution 1: LazyDataModel init

In this solution, I achieve the correct behavior. Let's say I load the table without any filters applied. I get 100 results, then I navigate to page 2.

When I perform a search, I get a new instance of LazyDataModel with the page set to 0, ensuring that the correct query is performed.

What I dislike about this solution is that I have to "hardcode" the form name and table name in my init method. If I change the form name, everything stops working.

public LazyContractDto(Service service, ContractFilter filter) {
        this.service = service;
        this.contractFilter = contractFilter;
        DataTable dataTable = (DataTable) FacesContext.getCurrentInstance().getViewRoot().findComponent("contracts:contractsTable");
        dataTable.setFirst(0);
    }

solution 2: xhtml, onclick pfWidget

In my search button, I perform an action onclick. The issue in this case is that the load method is invoked twice, resulting in the query being performed twice.

The first time, I get no results because my API will perform this query:/contract/search?id=84&page=2&size=10. However, the second time, I get the correct result because the page size is set to 0 after the first load, and I'll perform this query: /contract/search?id=84&page=0&size=10, retrieving the few contracts that my API returns.

onclick="PF('contractsTable').getPaginator().setPage(0);"

0

There are 0 answers