T5版本:tapestry-5.2.4
自定义PagedLoop组件的目的:
- 最主要的目的是对数据源进行分页。目前已公布的分页组件实现有两个,一个是chenillekit的PagedLoop, 一个是equanda版的JSPagedLoop。两者的区别是后者使用了ajax。
- 翻页时只从数据库中读取当前页的数据记录,而不是全部记录。
- 可以对读取的数据进行降序排列。
一、 源代码
PagedLoop.java
package org.example.components;
import java.util.Iterator;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.corelib.components.Delegate;
import org.apache.tapestry5.corelib.components.Loop;
import org.apache.tapestry5.corelib.data.GridPagerPosition;
import org.apache.tapestry5.util.StringToEnumCoercion;
import org.northstar.worklive.internal.LoopDataSource;
public class PagedLoop implements ClientElement {
//@Parameter(value = "prop:componentResources.id", defaultPrefix = "literal")
//private String clientId;
/**
* The element to render. If not null, then the loop will render the
* indicated element around its body (on each pass through the loop). The
* default is derived from the component template.
*/
@Parameter(value = "prop:componentResources.elementName", defaultPrefix = "literal")
private String elementName;
/**
* The element to render. If not null, then the loop will render the
* indicated element around its body (on each pass through the loop). The
* default is derived from the component template.
*/
@Parameter(required = true, principal = true, autoconnect = true)
private LoopDataSource source;
/**
* A wrapper around the provided Data Source that caches access to the
* availableRows property. This is the source provided to sub-components.
*/
private LoopDataSource cachingSource;
/**
* Defines where the pager (used to navigate within the "pages" of results)
* should be displayed: "top", "bottom", "both" or "none".
*/
@Parameter(value = "bottom", defaultPrefix = "literal" )
private String pagerPosition;
private GridPagerPosition internalPagerPosition;
/**
* The number of rows of data displayed on each page. If there are more rows
* than will fit, the Grid will divide up the rows into "pages" and
* (normally) provide a pager to allow the user to navigate within the
* overall result set.
*/
@Parameter("25")
private int rowsPerPage;
@Persist
private int currentPage;
/**
* The current value, set before the component renders its body.
*/
@SuppressWarnings("unused")
@Parameter
private Object value;
/**
*If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off.
* Defaults to false, enabling state saving logic within Forms.
*/
@SuppressWarnings("unused")
@Parameter(name = "volatile")
private boolean volatileState;
/**
* The index into the source items.
*/
@SuppressWarnings("unused")
@Parameter
private int index;
/**
* Optional primary key converter; if provided and inside a form and not
* volatile, then each iterated value is converted and stored into the form.
*/
@SuppressWarnings("unused")
@Parameter
private ValueEncoder<?> encoder;
@SuppressWarnings("unused")
@Component(parameters = { "source=dataSource",
"elementName=prop:elementName", "value=inherit:value",
"volatile=inherit:volatileState", "encoder=inherit:encoder",
"index=inherit:index" })
private Loop loop;
@Component(parameters = { "source=dataSource", "rowsPerPage=rowsPerPage",
"currentPage=currentPage" })
private Pager pager;
@SuppressWarnings("unused")
@Component(parameters = "to=pagerTop")
private Delegate pagerTop;
@SuppressWarnings("unused")
@Component(parameters = "to=pagerBottom")
private Delegate pagerBottom;
/**
* A Block to render instead of the table (and pager, etc.) when the source
* is empty. The default is simply the text "There is no data to display".
* This parameter is used to customize that message, possibly including
* components to allow the user to create new objects.
*/
@Parameter(value = "block:empty")
private Block empty;
private String assignedClientId;
@Parameter(name="OrderBy", defaultPrefix = "literal")
private String propertyName;
/**
* A version of LoopDataSource that caches the availableRows property. This addresses TAPESTRY-2245.
*/
static class CachingDataSource implements LoopDataSource
{
private final LoopDataSource delegate;
private boolean availableRowsCached;
private int availableRows;
CachingDataSource(LoopDataSource delegate)
{
this.delegate = delegate;
}
public int getTotalRowCount()
{
if (!availableRowsCached)
{
availableRows = delegate.getTotalRowCount();
availableRowsCached = true;
}
return availableRows;
}
public void prepare(int startIndex, int endIndex, String propertyName)
{
delegate.prepare(startIndex, endIndex, propertyName);
}
/*public Object getRowValue(int index)
{
return delegate.getRowValue(index);
}
public Class<?> getRowType()
{
return delegate.getRowType();
}*/
@Override
public Iterator<Object> iterator() {
return delegate.iterator();
}
}
public String getElementName() {
return elementName;
}
public Object getPagerTop() {
return internalPagerPosition.isMatchTop() ? pager : null;
}
public Object getPagerBottom() {
return internalPagerPosition.isMatchBottom() ? pager : null;
}
public int getRowsPerPage() {
return rowsPerPage;
}
public void setRowsPerPage(int rowsPerPage) {
this.rowsPerPage = rowsPerPage;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
void setupDataSource()
{
cachingSource = new CachingDataSource(source);
int availableRows = cachingSource.getTotalRowCount();
if (availableRows == 0)
return;
int maxPage = ((availableRows - 1) / rowsPerPage) + 1;
// This captures when the number of rows has decreased, typically due to deletions.
int effectiveCurrentPage = getCurrentPage();
if (effectiveCurrentPage > maxPage)
effectiveCurrentPage = maxPage;
int startIndex = (effectiveCurrentPage - 1) * rowsPerPage;
int endIndex = Math.min(startIndex + rowsPerPage - 1, availableRows - 1);
cachingSource.prepare(startIndex, endIndex, propertyName);
}
Object setupRender() {
if (currentPage == 0)
currentPage = 1;
internalPagerPosition = new StringToEnumCoercion<GridPagerPosition>(
GridPagerPosition.class).coerce(pagerPosition);
setupDataSource();
// If there's no rows, display the empty block placeholder.
return cachingSource.getTotalRowCount() == 0 ? empty : null;
}
Object beginRender() {
// Skip rendering of component (template, body, etc.) when there's
// nothing to display.
// The empty placeholder will already have rendered.
return (cachingSource.getTotalRowCount() != 0);
}
void onAction(int newPage){
currentPage = newPage;
}
/**
* Returns a unique id for the element. This value will be unique for any given rendering of a
* page. This value is intended for use as the id attribute of the client-side element, and will
* be used with any DHTML/Ajax related JavaScript.
*/
@Override
public String getClientId()
{
return assignedClientId;
}
public LoopDataSource getDataSource(){
return cachingSource;
}
public String getPropertyName(){
return propertyName;
}
public void setPropertyName(String name){
propertyName = name;
}
}
PagedLoop.tml
<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> <div t:id="pagerTop"/> <t:loop t:id="loop"><t:body/></t:loop> <div t:id="pagerBottom"/> <t:block> <div t:id="pager"/> </t:block> <t:block id="empty">${message:empty.list}</t:block> </t:container>
Pager.java(是复制GridPager.java过来的,只不过修改了source参数的数据类型)
package org.example.components;
import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.grid.GridDataSource;
import org.apache.tapestry5.internal.InternalConstants;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.ClientBehaviorSupport;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.northstar.worklive.internal.LoopDataSource;
/**
* Generates a series of links used to jump to a particular page index within the overall data set.
*/
@Events(InternalConstants.GRID_INPLACE_UPDATE + " (internal event)")
public class Pager
{
/**
* The source of the data displayed by the grid (this is used to determine {@link GridDataSource#getAvailableRows()
* how many rows are available}, which in turn determines the page count).
*/
@Parameter(required = true)
private LoopDataSource source;
/**
* The number of rows displayed per page.
*/
@Parameter(required = true)
private int rowsPerPage;
/**
* The current page number (indexed from 1).
*/
@Parameter(required = true)
private int currentPage;
/**
* Number of pages before and after the current page in the range. The pager always displays links for 2 * range + 1
* pages, unless that's more than the total number of available pages.
*/
@Parameter("5")
private int range;
/**
* If not null, then each link is output as a link to update the specified zone.
*/
@Parameter
private String zone;
private int lastIndex;
private int maxPages;
@Inject
private ComponentResources resources;
@Inject
private Messages messages;
@Environmental
private ClientBehaviorSupport clientBehaviorSupport;
@Environmental
private JavaScriptSupport jsSupport;
void beginRender(MarkupWriter writer)
{
int availableRows = source.getTotalRowCount();
maxPages = ((availableRows - 1) / rowsPerPage) + 1;
if (maxPages < 2) return;
writer.element("div", "class", "t-data-grid-pager");
lastIndex = 0;
for (int i = 1; i <= 2; i++)
writePageLink(writer, i);
int low = currentPage - range;
int high = currentPage + range;
if (low < 1)
{
low = 1;
high = 2 * range + 1;
}
else
{
if (high > maxPages)
{
high = maxPages;
low = high - 2 * range;
}
}
for (int i = low; i <= high; i++)
writePageLink(writer, i);
for (int i = maxPages - 1; i <= maxPages; i++)
writePageLink(writer, i);
writer.end();
}
private void writePageLink(MarkupWriter writer, int pageIndex)
{
if (pageIndex < 1 || pageIndex > maxPages) return;
if (pageIndex <= lastIndex) return;
if (pageIndex != lastIndex + 1) writer.write(" ... ");
lastIndex = pageIndex;
if (pageIndex == currentPage)
{
writer.element("span", "class", "current");
writer.write(Integer.toString(pageIndex));
writer.end();
return;
}
Object[] context = zone == null
? new Object[] { pageIndex }
: new Object[] { pageIndex, zone };
Link link = resources.createEventLink(EventConstants.ACTION, context);
Element element = writer.element("a",
"href", zone == null ? link : "#",
"title", messages.format("goto-page", pageIndex));
writer.write(Integer.toString(pageIndex));
writer.end();
if (zone != null)
{
String id = jsSupport.allocateClientId(resources);
element.attribute("id", id);
clientBehaviorSupport.linkZone(id, zone, link);
}
}
/**
* Normal, non-Ajax event handler.
*/
void onAction(int newPage)
{
// TODO: Validate newPage in range
currentPage = newPage;
}
/**
* Akjax event handler, passing the zone along.
*/
boolean onAction(int newPage, String zone)
{
onAction(newPage);
resources.triggerEvent(InternalConstants.GRID_INPLACE_UPDATE, new Object[] { zone }, null);
return true; // abort event
}
}
数据源接口:LoopDataSource.java
package org.example.internal;
public interface LoopDataSource extends Iterable<Object>{
int getTotalRowCount();
void prepare(int startIndex, int endIndex, String propertyName);
//Object getRowValue(int index);
//Class<?> getRowType();
}
数据源接口实现:HibernateLoopDataSource.java
package org.example.internal;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
public class HibernateLoopDataSource implements LoopDataSource {
private final Session session;
private final Class<?> entityType;
//private int startIndex;
private List<Object> preparedResults;
public HibernateLoopDataSource(Session session, Class<?> entityType){
assert session != null;
assert entityType != null;
this.session = session;
this.entityType = entityType;
}
public int getTotalRowCount(){
Criteria criteria = session.createCriteria(entityType);
criteria.setProjection(Projections.rowCount());
Number result = (Number) criteria.uniqueResult();
return result.intValue();
}
@SuppressWarnings("unchecked")
public void prepare(int startIndex, int endIndex, String propertyName){
Criteria crit = session.createCriteria(entityType);
crit.setFirstResult(startIndex).setMaxResults(endIndex - startIndex + 1);
if(propertyName!=null){
crit.addOrder(Order.desc(propertyName));}
//this.startIndex = startIndex;
preparedResults = crit.list();
}
/*public Object getRowValue(int index)
{
return preparedResults.get(index - startIndex);
}
*//**
* Returns the entity type, as provided via the constructor.
*//*
public Class<?> getRowType()
{
return entityType;
}
*/
@Override
public Iterator<Object> iterator() {
return preparedResults.iterator();
}
}
二、应用
<t:pagedLoop source="blogs" rowsperpage="10" value="blog"/>
@Inject private Session session; //Hibernate session
@Property
private Blog blog; //Entity
public LoopDataSource getBlogs(){
return new HibernateLoopDataSource(session, Blog.class);
}