Skip to content

Commit

Permalink
Support request scoped beans and stateless views in op:dataTable
Browse files Browse the repository at this point in the history
  • Loading branch information
BalusC committed May 9, 2020
1 parent 0bb53c7 commit 61c97cc
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 24 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -110,7 +110,7 @@
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.2</version><!-- minimum version for DataTable#isFilterable() and DataTable#getNullSortOrder() -->
<version>7.0</version><!-- minimum version for properly resolving table sort fields in DataTableRenderer -->
<scope>provided</scope><!-- by enduser (of course enduser can provide a newer version) -->
</dependency>

Expand Down
@@ -0,0 +1,41 @@
/*
* Copyright 2020 OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.optimusfaces.component;

import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;

import org.omnifaces.optimusfaces.model.LazyPagedDataModel;
import org.primefaces.component.datatable.DataTable;

/**
* <p>
* This extended data table is already automatically registered via our <code>faces-config.xml</code>.
* This will preload lazy loaded model for decode against a request scoped bean or a stateless view.
*/
public class ExtendedDataTable extends DataTable {

@Override
protected void preDecode(FacesContext context) {
if (context.isPostback() && isLazy()) {
DataModel<?> model = getDataModel();

if (model instanceof LazyPagedDataModel && model.getWrappedData() == null) {
((LazyPagedDataModel<?>) model).preloadPage(context, this);
}
}

super.preDecode(context);
}

}
Expand Up @@ -122,26 +122,38 @@ public class LazyPagedDataModel<E extends Identifiable<?>> extends LazyDataModel
public List<E> load(int offset, int limit, String tableSortField, SortOrder tableSortOrder, Map<String, Object> tableFilters) {
FacesContext context = getContext();
DataTable table = getTable();
loadPage(context, table, tableSortField, tableSortOrder, tableFilters);
updateQueryStringIfNecessary(context);

return list;
}

public void preloadPage(FacesContext context, DataTable table) {
loadPage(context, table, null, null, emptyMap());
setWrappedData(list);
setRowCount(list.getEstimatedTotalNumberOfResults());
setPageSize(table.getRows());
}

private void loadPage(FacesContext context, DataTable table, String tableSortField, SortOrder tableSortOrder, Map<String, Object> tableFilters) {
List<UIColumn> processableColumns = table.getColumns().stream().filter(this::isProcessableColumn).collect(toList());

updateQueryString = parseBoolean(String.valueOf(table.getAttributes().get("updateQueryString")));
queryParameterPrefix = coalesce((String) table.getAttributes().get("queryParameterPrefix"), "");
ordering = processPageAndOrdering(context, table, limit, tableSortField, tableSortOrder);
ordering = processPageAndOrdering(context, table, tableSortField, tableSortOrder);
filters = processFilters(context, table, processableColumns, tableFilters);
globalFilter = processGlobalFilter(context, table, tableFilters);
selection = processSelectionIfNecessary(context, selection);

loadPage(table, processableColumns, limit);
updateQueryStringIfNecessary(context);

return list;
loadPage(table, processableColumns);
}

private void loadPage(DataTable table, List<UIColumn> processableColumns, int limit) {
private void loadPage(DataTable table, List<UIColumn> processableColumns) {
Map<String, Object> requiredCriteria = processRequiredCriteria(processableColumns);
Map<String, Object> optionalCriteria = processOptionalCriteria(processableColumns);

int offset = table.getFirst();
int limit = table.getRows();
boolean pageOfSameCriteria = requiredCriteria.equals(page.getRequiredCriteria()) && optionalCriteria.equals(page.getOptionalCriteria());
boolean nextOrPreviousPageOfSameCriteria = pageOfSameCriteria && !isEmpty(list) && abs(offset - page.getOffset()) == limit && ordering.equals(page.getOrdering());
boolean previousPageOfSameCriteria = nextOrPreviousPageOfSameCriteria && offset < page.getOffset();
Expand Down Expand Up @@ -193,15 +205,15 @@ protected boolean isProcessableColumn(UIColumn column) {
return Boolean.parseBoolean(String.valueOf(table.getAttributes().get("searchable")));
}

protected LinkedHashMap<String, Boolean> processPageAndOrdering(FacesContext context, DataTable table, int limit, String tableSortField, SortOrder tableSortOrder) {
protected LinkedHashMap<String, Boolean> processPageAndOrdering(FacesContext context, DataTable table, String tableSortField, SortOrder tableSortOrder) {
LinkedHashMap<String, Boolean> ordering = new LinkedHashMap<>(2);

if (!context.isPostback()) {
if (list == null) {
String page = getTrimmedQueryParameter(context, queryParameterPrefix + QUERY_PARAMETER_PAGE);

if (!isEmpty(page)) {
try {
table.setFirst((Integer.valueOf(page) - 1) * limit);
table.setFirst((Integer.valueOf(page) - 1) * table.getRows());
}
catch (NumberFormatException ignore) {
//
Expand Down Expand Up @@ -231,7 +243,7 @@ protected LinkedHashMap<String, Boolean> processPageAndOrdering(FacesContext con
}

Entry<String, Boolean> defaultOrder = defaultOrdering.entrySet().iterator().next();
table.setSortField(coalesce(sortField, defaultOrder.getKey(), tableSortField));
table.setSortField(coalesce(sortField, tableSortField, defaultOrder.getKey()));
table.setSortOrder(coalesce(sortOrder, sortField != null ? tableSortOrder : defaultOrder.getValue() ? ASCENDING : DESCENDING).name());
}
else if (!isEmpty(tableSortField)) {
Expand Down Expand Up @@ -398,7 +410,7 @@ protected void updateQueryStringIfNecessary(FacesContext context) {
selection.stream().sorted().forEach(entity -> params.add(new SimpleParam(queryParameterPrefix + QUERY_PARAMETER_SELECTION, entity.getId())));
}

oncomplete("OptimusFaces.Util.historyReplaceQueryString('" + Servlets.toQueryString(params) + "')");
oncomplete("OptimusFaces.Util.updateQueryString('" + Servlets.toQueryString(params) + "')");
}


Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/META-INF/faces-config.xml
Expand Up @@ -30,4 +30,9 @@
<application>
<el-resolver>org.omnifaces.optimusfaces.el.NestedBaseEntityELResolver</el-resolver>
</application>

<component>
<component-type>org.primefaces.component.DataTable</component-type>
<component-class>org.omnifaces.optimusfaces.component.ExtendedDataTable</component-class>
</component>
</faces-config>
Expand Up @@ -56,6 +56,16 @@ OptimusFaces.Util = (function(window, document) {
}
}

self.updateQueryString = function(queryString) {
self.historyReplaceQueryString(queryString);
for (var i = 0; i < document.forms.length; i++) {
var form = document.forms[i];
if (form["javax.faces.ViewState"]) {
form.action = form.action.split(/\?/, 2)[0] + (queryString ? "?" : "") + queryString;
}
}
}

self.updateQueryStringParameter = function(url, name, value) {
var parts = url.split(/#/, 2);
var uri = parts[0];
Expand Down
76 changes: 68 additions & 8 deletions src/test/java/org/omnifaces/optimusfaces/test/OptimusFacesIT.java
Expand Up @@ -29,6 +29,7 @@
import static org.omnifaces.optimusfaces.model.PagedDataModel.QUERY_PARAMETER_ORDER;
import static org.omnifaces.optimusfaces.model.PagedDataModel.QUERY_PARAMETER_PAGE;
import static org.omnifaces.optimusfaces.model.PagedDataModel.QUERY_PARAMETER_SEARCH;
import static org.omnifaces.optimusfaces.model.PagedDataModel.QUERY_PARAMETER_SELECTION;
import static org.omnifaces.optimusfaces.test.service.StartupService.ROWS_PER_PAGE;
import static org.omnifaces.optimusfaces.test.service.StartupService.TOTAL_RECORDS;
import static org.omnifaces.persistence.Database.POSTGRESQL;
Expand Down Expand Up @@ -239,7 +240,7 @@ protected String getQueryParameter(String name) {
}

protected int getRowCount() {
return Integer.parseInt(browser.findElement(By.id("rowCount")).getText());
return Integer.parseInt(rowCount.getText());
}

protected static boolean isWildFly() {
Expand Down Expand Up @@ -355,6 +356,12 @@ protected boolean isLazy() {
@FindBy(css="#form\\:table_data tr")
private List<WebElement> rows;

@FindBy(css="#form\\:table_data tr:nth-child(5) td:first-child")
private WebElement fifthRow;

@FindBy(css="#form\\:table_data tr.ui-state-highlight")
private WebElement selectedRow;

@FindBy(css="#form\\:table_paginator_bottom span.ui-paginator-current")
private WebElement pageReport;

Expand Down Expand Up @@ -409,6 +416,12 @@ protected boolean isLazy() {
@FindBy(id="form:groups:3")
private WebElement criteriaGroupDEVELOPER;

@FindBy(id="rowCount")
private WebElement rowCount;

@FindBy(id="selection")
private WebElement selection;


// Tests ----------------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -467,15 +480,39 @@ public void testNonLazyFiltering() {
}

@Test
public void testLazyPagingSortingAndFiltering() {
public void testLazySelection() {
open("Lazy");
testPagingSortingAndFiltering();
testSelection();
}

@Test
public void testNonLazyPagingSortingAndFiltering() {
public void testNonLazySelection() {
open("NonLazy");
testPagingSortingAndFiltering();
testSelection();
}

@Test
public void testLazyPagingSortingFilteringAndSelection() {
open("Lazy");
testPagingSortingFilteringAndSelection();
}

@Test
public void testNonLazyPagingSortingFilteringAndSelection() {
open("NonLazy");
testPagingSortingFilteringAndSelection();
}

@Test
public void testLazyStatelessPagingSortingFilteringAndSelection() {
open("LazyStateless");
testPagingSortingFilteringAndSelection();
}

@Test
public void testNonLazyStatelessPagingSortingFilteringAndSelection() {
open("NonLazyStateless");
testPagingSortingFilteringAndSelection();
}

@Test
Expand Down Expand Up @@ -706,7 +743,17 @@ protected void testFiltering() {
assertPaginatorState(1, TOTAL_RECORDS);
}

protected void testPagingSortingAndFiltering() {
protected void testSelection() {
guardAjax(fifthRow).click();
assertSelectedState(196);

guardAjax(pageNext).click();
guardAjax(fifthRow).click();
assertPaginatorState(2);
assertSelectedState(186);
}

protected void testPagingSortingFilteringAndSelection() {
guardAjax(pageNext).click();
assertPaginatorState(2);

Expand All @@ -719,18 +766,22 @@ protected void testPagingSortingAndFiltering() {
assertFilteredState(emailColumnFilter, "1");
assertSortedState(emailColumn, true);

for (int nextPage = 2; nextPage <= 10; nextPage++) {
for (int nextPage = 2; nextPage <= 5; nextPage++) {
guardAjax(pageNext).click();
assertPaginatorState(nextPage, 119);
assertFilteredState(emailColumnFilter, "1");
assertSortedState(emailColumn, true);
guardAjax(fifthRow).click();
assertSelectedState(96 + nextPage * 9);
}

for (int previousPage = 9; previousPage >= 1; previousPage--) {
for (int previousPage = 4; previousPage >= 1; previousPage--) {
guardAjax(pagePrevious).click();
assertPaginatorState(previousPage, 119);
assertFilteredState(emailColumnFilter, "1");
assertSortedState(emailColumn, true);
guardAjax(fifthRow).click();
assertSelectedState(96 + previousPage * 9);
}

guardAjax(emailColumn).click();
Expand Down Expand Up @@ -791,6 +842,9 @@ protected void testQueryStringLoading(String type) {
open(type, "o=dateOfBirth");
assertPaginatorState(1);
assertSortedState(dateOfBirthColumn, true);

open(type, "s=195");
assertSelectedState(195);
}

protected void testCriteria() {
Expand Down Expand Up @@ -1281,6 +1335,12 @@ protected void assertFilteredState(WebElement filter, String filterValue) {
assertTrue(field + " filtering " + actualValues + " matches " + filterValue, actualValues.stream().allMatch(value -> value.contains(filterValue)));
}

protected void assertSelectedState(int selectedId) {
assertTrue(selectedId + " must be selected", selectedRow.findElement(By.cssSelector("td:first-child")).getText().equals(String.valueOf(selectedId)));
assertTrue(selectedId + " must be in the selection", selection.getText().equals("[Person[" + selectedId + "]]"));
assertEquals("select query string", String.valueOf(selectedId), getQueryParameter(QUERY_PARAMETER_SELECTION));
}

protected void assertGlobalFilterState(String filterValue) {
String actualFilterValue = globalFilter.getAttribute("value");
assertEquals("filter value", filterValue, actualFilterValue);
Expand Down
@@ -0,0 +1,42 @@
/*
* Copyright 2020 OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.optimusfaces.test.view;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.omnifaces.optimusfaces.model.PagedDataModel;
import org.omnifaces.optimusfaces.test.model.Person;
import org.omnifaces.optimusfaces.test.service.PersonService;

@Named
@RequestScoped
public class OptimusFacesITLazyStatelessBean {

private PagedDataModel<Person> lazyPersons;

@Inject
private PersonService personService;

@PostConstruct
public void init() {
lazyPersons = PagedDataModel.lazy(personService).build();
}

public PagedDataModel<Person> getLazyPersons() {
return lazyPersons;
}

}

0 comments on commit 61c97cc

Please sign in to comment.