Posts Tagged ‘sorting

22
May
11

JSF Datatable with database sorting, filtering and pagination

When database grows (or may grow) significantly it is necessary to consider database pagination for loading data since the Application Server memory isn’t sized for handling such huge amount of data.

Luckily rich:extendedDataTable provides an easy way of delegating the sorting, filtering and also pagination to the underlying data store. I sincerely remember that the I saw the base for this idea somewhere in Seam Framework forum (if anyone knows something similar please let me know) but I couldn’t find it for referencing, anyways it is now extended and more flexible. Another foundation of this technique is the org.jboss.seam.framework.Query since it provides means of expressing filtering and sorting as a huge string even with JSF variables (eg.: #{something.otherthing}).

Lets start with the EntityExtendedTableDataModel that is responsible for passing filtering and sorting data from view layer to data layer:

<pre>/**
EntityExtendedTableDataModel - Database pagination, sorting and filtering for richfaces datatables
Copyright (C) 2011 Rafael Ribeiro

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/</pre>
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.component.html.HtmlInputText;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import org.apache.commons.lang.StringUtils;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Expressions.ValueExpression;
import org.jboss.seam.framework.Query;
import org.jboss.seam.ui.AbstractEntityLoader;
import org.richfaces.model.DataProvider;
import org.richfaces.model.ExtendedFilterField;
import org.richfaces.model.ExtendedTableDataModel;
import org.richfaces.model.FilterField;
import org.richfaces.model.Modifiable;
import org.richfaces.model.Ordering;
import org.richfaces.model.SortField2;

public class EntityExtendedTableDataModel extends ExtendedTableDataModel implements Modifiable {
EntityDataProvider entityDataProvider;
public EntityExtendedTableDataModel(Query query) {
super(new EntityDataProvider(query));
entityDataProvider = (EntityDataProvider) getDataProvider();
}

public void modify(List<FilterField> filterFields,
List<SortField2> sortFields) {
performSort(sortFields);
performFilter(filterFields);
}

private void performSort(List<SortField2> sortFields) {
StringBuilder order = new StringBuilder();
Pattern p = Pattern.compile("\\#\\{(.+)\\}");
for (SortField2 s: sortFields) {
if (Ordering.UNSORTED.equals(s.getOrdering()) == false) {
String expr = s.getExpression().getExpressionString();
Matcher m = p.matcher(expr);
if (m.matches()) { //remove the #{} otherwise richfaces wont trigger the sort event
order.append(m.group(1));
}
else {
order.append(expr);
}
order.append(" ");
order.append(Ordering.ASCENDING.equals(s.getOrdering()) ? "ASC" : "DESC");
order.append(", ");
}
}
if (order.length() > 0)
entityDataProvider.getQuery().setOrder(order.delete(order.length()-2, order.length()).toString());
}
//allows us to specify filter as o.name == #{exampleEntity.name}
private void performFilter(List<FilterField> filterFields) {
Expressions expressions = new org.jboss.seam.core.Expressions();
Pattern p = Pattern.compile(".*(\\#\\{.+\\}).*");
List<String> restrictions = new ArrayList<String>();
for (FilterField f: filterFields) {
ExtendedFilterField e = (ExtendedFilterField) f;
if (StringUtils.isEmpty(e.getFilterValue()))
continue;
StringBuilder filter = new StringBuilder();
String expr = e.getExpression().getExpressionString();
Matcher m = p.matcher(expr);
if (!m.matches())
continue;
ValueExpression ve = expressions.createValueExpression(m.group(1));
FacesContext ctx = FacesContext.getCurrentInstance();
Converter c = ctx.getApplication().createConverter(ve.getType());
if (c == null)
ve.setValue(e.getFilterValue());
else {
try {
ve.setValue(c.getAsObject(ctx, new HtmlInputText(), e.getFilterValue()));
} catch (ConverterException ce) {
continue;
}
}
restrictions.add(expr);
}
entityDataProvider.getQuery().setRestrictionExpressionStrings(restrictions);
}

}
class EntityDataProvider implements DataProvider {

private Query query;
public EntityDataProvider(Query query) {
this.query = query;
}

public Object getItemByKey(Object key) {
return AbstractEntityLoader.instance().get(String.valueOf(key));
}

public List getItemsByRange(int firstRow, int endRow) {
query.setFirstResult(firstRow);
query.setMaxResults(endRow-firstRow);
return query.getResultList();
}

public Object getKey(Object item) {
return AbstractEntityLoader.instance().put(item);
}

public int getRowCount() {
return query.getResultCount().intValue();
}
protected Query getQuery() {
return query;
}
}

Now we have to replace Seam datamodels component with ours:

<pre>EntityExtendedTableDataModel - Database pagination, sorting and filtering for richfaces datatables
Copyright (C) 2011 Rafael Ribeiro

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/</pre>
import static org.jboss.seam.ScopeType.STATELESS;
import static org.jboss.seam.annotations.Install.FRAMEWORK;

import javax.faces.model.DataModel;

import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.faces.DataModels;
import org.jboss.seam.framework.EntityQuery;
import org.jboss.seam.framework.Query;

@Name("org.jboss.seam.faces.dataModels")
@Install(precedence=FRAMEWORK)
@Scope(STATELESS)
@BypassInterceptors
public class RichFacesModels extends DataModels
{

@Override
public DataModel getDataModel(Query query)
{
if (query instanceof EntityQuery)
{
return new EntityExtendedTableDataModel((EntityQuery) query);
}
else
{
return super.getDataModel(query);
}
}

}

Finally we specify in rich:column the filtering remembering that the base object name here must be in sync with the referenced query, example:


<rich:extendedDataTable id="listaPacientes" rows="10" value="#{queryAllEntities.dataModel}" var="obj">
<rich:column sortable="true" sortBy="#{o.name}" selfSorted="false"
filterBy="lower(o.name) like concat(lower(#{exampleEntity.name}),'%')" filterEvent="onkeyup"
label="Name">

The trick here is to specify selfSorted=”false” and adding the JSF #{} part for the sorting to work, this way o.name will be appended to Query sorting.

On the other hand, for filtering to work you only need to specify your query, note that what you specify between the #{} will hold the filter data handed to the Query.

Also note that you need to have a query and an entity as a component specified in components.xml, in this example, this could be the query and the example entity:


<component name="exampleEntity" class="br.com.rafaelri.MyEntity" scope="session" />
<framework:entity-query name="queryAllEntities" ejbql="select o from MyEntity o" />

This, combined with rich:dataScroller will result in sorting, pagination and filtering handled by the Database.




ClustrMaps

Blog Stats

  • 357,125 hits since aug'08

%d bloggers like this: