[Tapestry5] 关于自定义分页的Loop组件:PagedLoop实现

Linuxboy 2011-01-22
我根据Grid的实现方式,写了一个pagedloop组件,设计了一个LoopDataSource接口作为pagedloop的source,主要是希望从数据库中抓取每页所需的记录,而不是所有记录。
LoopDataSource.java:
public interface LoopDataSource extends Iterable<Object>{
	
	int getTotalRowCount();
	
	void prepare(int startIndex, int endIndex, String propertyName);
}

HibernateLoopDataSource.java
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();
    }
    
   
	@Override
	public Iterator<Object> iterator() {
		// TODO Auto-generated method stub
		return preparedResults.iterator();
	}
}



PagedLoop.java:
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. 
     */
    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);
        }

        
		@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;
    }
}


@Property
private Blog blog;
@Inject
private Session session;

public LoopDataSource getList(){
return new HibernateLoopDataSource(session, Blog.class);
}

<t:pagedloop source="list" value="blog" rowsperpage="10" orderby="postDate"/>

引用
这个组件运行正常。但是,我希望从数据库获取的数据可以根据blog的某个字段逆序排列,不知应该如何实现?

引用
已修正pagedloop组件,增加orderby参数。
Linuxboy 2011-01-23
给pagedloop加上一个orderby参数,只要orderby不为空,就按orderby参数提供的字段降序排列。
同时修正loopdatasource方法为prepare(int startIndex, int endIndex, String propertyName).

修改内容如上。
lya_love 2011-01-23
源码 分享 一下撒下 啊  嘻嘻!
Linuxboy 2011-01-23
上面就是源码啊,至于pagedloop中用到的Pager,代码如下:
public class Pager {
	/**
	 * The source of the data displayed by the PagedList (this is used to
	 * determine
	 * {@link LoopDataSource#getTotalRowCount() 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;
	
	/**
     * If this pager is in a custom position it must provide the id of the
     * PagedLoop it is associated with.
     */
    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    private String _for;

	/**
	 * 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;

	private int lastIndex;

	private int maxPages;

	//private String pagedLoopElementName;

	@Inject
	private ComponentResources resources;

	@Inject
	private Messages messages;
	
	private Component pagedLoopComponent;
	
	private PagedLoop pagedLoop;
	
	void setupRender() {
		if (_for != null)
        {
            source = getPagedLoop().getPagedSource();
            rowsPerPage = getPagedLoop().getRowsPerPage();
            currentPage = getPagedLoop().getCurrentPage();
		}

	}

	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();
		//writer.end();
	}
	
	/**
	 * Normal, non-Ajax event handler.
	 */
	void onAction(int newPage)
    {
        // TODO: Validate newPage in range
        currentPage = newPage;
        if (_for != null)
        {
            getPagedLoopComponent().getComponentResources().triggerEvent(
                    EventConstants.ACTION, new Integer[]{newPage},
                    null);
        }
    }

    private Component getPagedLoopComponent()
    {
        if (_for != null && pagedLoopComponent == null)
        {
            pagedLoopComponent = resources.getPage().getComponentResources().getEmbeddedComponent(_for);
        }
        return pagedLoopComponent;
    }

    private PagedLoop getPagedLoop()
    {
        if (_for != null && pagedLoop == null)
        {
            pagedLoop = (PagedLoop) getPagedLoopComponent();
        }
        return pagedLoop;
    }

	private void writePageLink(MarkupWriter writer, int pageIndex) {

		if (pageIndex < 1 || pageIndex > maxPages)
			return;

		if (pageIndex <= lastIndex)
			return;

		if (pageIndex != lastIndex + 1)
			writer.write(" ... "); // &#8230; is ellipsis

		lastIndex = pageIndex;

		if (pageIndex == currentPage) {
			writer.element("span", "class", "current");
			writer.write(Integer.toString(pageIndex));
			writer.end();
			return;
		}

		Link link = resources.createEventLink(EventConstants.ACTION, pageIndex);

		writer.element("a", "href", link, "title", messages.format("goto-page",
				pageIndex));

		writer.write(Integer.toString(pageIndex));
		writer.end();

	}
}
ibatismm 2013-05-22
用Grid组件
oak2008 2013-11-12
如果要按照某些字段,某些条件进行多表关联查询排序,怎么处理?
Global site tag (gtag.js) - Google Analytics