自定义分页Loop组件:PagedLoop

T5版本:tapestry-5.2.4

自定义PagedLoop组件的目的:

 

  1. 最主要的目的是对数据源进行分页。目前已公布的分页组件实现有两个,一个是chenillekit的PagedLoop, 一个是equanda版的JSPagedLoop。两者的区别是后者使用了ajax。
  2. 翻页时只从数据库中读取当前页的数据记录,而不是全部记录。
  3. 可以对读取的数据进行降序排列。
一、 源代码
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);
}

你可能感兴趣的:(apache,Ajax,Hibernate,IOC,tapestry)