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=" " />
<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);"