Paging Sorting with a filter object

From Documentation
Revision as of 03:52, 13 September 2010 by Elton776 (talk | contribs) (Created page with '{{Template:Smalltalk_Author| |author=Dipl.rer.pol. Stephan Gerth, develops bigger database applications over the last 20 years. |date=May 20, 2009 |version=ZK 3.6.x }} =Prefac…')
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
DocumentationSmall Talks2009MayPaging Sorting with a filter object
Paging Sorting with a filter object

Author
Dipl.rer.pol. Stephan Gerth, develops bigger database applications over the last 20 years.
Date
May 20, 2009
Version
ZK 3.6.x

Preface

The ZKoss paging component by default works with the complete list of loaded records. To work with huge tables we need to modify the paging component so that it shows only the exact count of records that the user can see in the page of the listbox. By paging we intercept the paging event and do a special call to the database where we can overhanded the page and the pagesize (count of pulled records). So we became page for page only subsets of the result.

The interesting part to solve is to work with a database oriented paging mechanism AND accept changes of the sort-order triggered by the listheaders.

For an overview how the pagination can be coded look at the Smalltalk from Marcos de Sousa: Use Load-On-Demand to Handle Hugh Data. http://docs.zkoss.org/wiki/Use_Load-On-Demand_to_Handle_Huge_Data.

In short: Pagination is a block wise loading of records.

Advantage:

  • reduces DB work;
  • reduces network traffic between Database and the application server;
  • reduces cpu and memory demand
  • reduces the response time;
  • increase the data throughput

Problem Definition

If we want database paged records from a table and would like to allow changing the sort order of these data by clicking on the listheaders we have to consider two things:

  • First we must do consequently the sorting on the database to prevent users confusion if the resultset sorted in the grid.
  • Secondly we need the right place in code where we can catch which of the listheaders is clicked for changing the sorting.
  • Third we need a mechanism to remember what is the originally query that should now be modified for sorting in an other sort order.


Why we need this?

Let us consider what we want from the database side: First we have a sql statement with a where clause. Second we want to add/manipulate the order by clause. By changing the sort order we need to remember what is in the select clause and in the where clause.

We can store pieces of the sql statement in separate strings and stick it together in the sequence we need for the new sort order.

Hmmmm. But what is when we do not work with direct sql statements and we would work with the Hibernate Criteria Classes. In all times we have do many lines to coding for storing/manipulating the sql stuff. Further in fine separated tiers we want not to have hibernate calls in the frontend.

Solution

So as we all times said: All is an object.

We do solve our problem with a helper class.

These helper class should do the following:

  • store the entity class
  • add/edit the where clause
  • add/edit the order by clause

So by sorting in the listbox we access all times these search object in which the select statement and the where clause are initially stored so we must modify only the sort by clause.

I remember me that I have a few days before seen a framework on code.google. It called Hibernate-Generic-DAO. See: http://code.google.com/p/hibernate-generic-dao/ So I have a second look on it and see that they work with a search object. This search object are filled in the frontend than overhanded to the backend and evaluated their. Going deeper in it I recognize that this class is exactly what we need. Unfortunately we already have our own Generic DAO Base class so we became problems to integrate only the search class from the framework.

I have contacted David Wolverton the developer of this framework. After a few emails he surprised me with a new version. In it he had separated the search class in an own package for easily using with zkoss or other frameworks. Absolute great thanks to David Wolverton.

The way is the goal

As playing with the search object i see that there is a need for a second helper class. By thinking over a migration for all our sample listboxes to do paging and sorting with the search object we need a wrapper class around the listbox. In which we can catch the paging and sorting events. So we can use these class for all of our listboxes.

The implementation

I will show the implementation with a search mask for customers. By writing this smalltalk I see there is a need for a third helper class if I will comfortably use it for other search masks. Above all if I will open the search mask new after closing it because for editing the old search definition.

I hope you have downloaded the newest jars from the Hibernate-Generic-DAO framework. Today the version 0.4.3 is actually with a separate search class package. You found the link for downloading ( http://code.google.com/p/hibernate-generic-dao/ ) on the right side under Links --> jars .


All components in the used zul-files are autowired, means all the window controllers are extended from a BaseController that implements AfterCompose where the components are binded. So in java-code we have full access to the used zkoss components over their component-id's.

Helper Classes:

  • PagedListWrapper.java / Helper class for easier working of changing the sort order of paged records. Catches the paging / sorting events.
  • SearchOperators.java / Extendable helper class for showing several search operators in a listbox.
  • SearchOperatorListModelItemRenderer.java / ItemRenderer for SearchOperator listbox.
  • HibernateSearchObject.java / capsulates the search object from the Hibernate-Generic-DAO framework.


Ok. Let』s begin.

1. The listbox

We have a listbox that』s shows a list of customers. In this sample we have created test data with 210.027 records. You can see the total Size in the right border on top. PageSize is 20 records per page. CustomerList 1.jpg


We fill the listbox initially when we created the window. We created a search object on which only the entity class is assigned and a initial sort order is set. The Hibernate-Generic-DAO framework doesn』t only give back a List of objects. It has a SearchResult object that holds the List and the Total Count of the query. So we call the second constructor of our PagedListWrapper and have the total Size of records too.


public void onCreate$window_customerList(Event event) throws Exception {
	. . .
	// ++ create the searchObject and init sorting ++//
	searchObj = new HibernateSearchObject(Kunde.class);
	searchObj.addSort("kunName1", false);
	setSearchObj(searchObj);

	// set the paging component params
	int pageSize = 20;
	pagingCustomerList.setPageSize(pageSize);
	pagingCustomerList.setDetailed(true);

	// Binded but not used listheaders must be declared like ->
	// lh.setSortAscending(""); lh.setSortDescending("")
	listheader_CustNo.setSortAscending(new FieldComparator("kunNr", true));
	listheader_CustNo.setSortDescending(new FieldComparator("kunNr", false));
	listheader_CustMatchcode.setSortAscending(new FieldComparator("kunMatchcode", true));
	listheader_CustMatchcode.setSortDescending(new FieldComparator("kunMatchcode", false));
	listheader_CustName1.setSortAscending(new FieldComparator("kunName1", true));
	listheader_CustName1.setSortDescending(new FieldComparator("kunName1", false));
	listheader_CustName2.setSortAscending(new FieldComparator("kunName2", true));
	listheader_CustName2.setSortDescending(new FieldComparator("kunName2", false));
	listheader_CustCity.setSortAscending(new FieldComparator("kunOrt", true));
	listheader_CustCity.setSortDescending(new FieldComparator("kunOrt", false));

	listBoxCustomer.setModel( new PagedListWrapper<Kunde>(listBoxCustomer, 
                                  pagingCustomerList, 
                                  getTestService().getSRBySearchObject( searchObj, 0, pageSize),
                                  searchObj));

	listBoxCustomer.setItemRenderer(new CustomerListModelItemRenderer());
	}


2. The search mask

The search mask have a few textbox components that corresponds to the table fields. At last there is a dropdown listbox that holds a list of branch objects that a customer can be assigned to. This is a relation on database side to the branch table. Between the label and the textboxes we have dropdown listboxes that holds all implemented filters by the SearchOperator.java helper class. See the documentation of the search object for more filters.

CustomerSearch 1.jpg

These filter operators are in a helper class called SearchOperators.java that can be extended. By pressing the search button we call our search method and added for every not empty input field a new filter type with the value to our search object.


/**
 * Search/filter data for the filled out fields<br>
 * <br>
 * 1. Checks for each textbox if there are a value. <br>
 * 2. Checks which operator is selected. <br>
 * 3. Store the filter and value in the searchObject. <br>
 * 4. Call the ServiceDAO method with searchObject as parameter. <br>
 */
@SuppressWarnings("unchecked")
public void doSearch() {

HibernateSearchObject<Kunde> so = new HibernateSearchObject(Kunde.class);

if (StringUtils.isNotEmpty(kunNr.getValue())) {

// get the search operator
Listitem item = sortOperator_kunNr.getSelectedItem();

if (item != null) {
		int searchOpId = ((SearchOperators) item.getAttribute("data")).getSearchOperatorId();

		if (searchOpId == Filter.OP_ILIKE) {
                       so.addFilter(new Filter("kunNr", "%" + kunNr.getValue().toUpperCase() + "%", searchOpId));
		} else if (searchOpId == -1) {
			// do nothing
		} else {
			so.addFilter(new Filter("kunNr", kunNr.getValue(), searchOpId));
		}
	}
	}


if (StringUtils.isNotEmpty(kunMatchcode.getValue())) {

// get the search operator	
Listitem item = sortOperator_kunMatchcode.getSelectedItem();

if (item != null) {
		      int searchOpId = ((SearchOperators) item.getAttribute("data")).getSearchOperatorId();

               if (searchOpId == Filter.OP_ILIKE) {
                         so.addFilter(new Filter("kunMatchcode", "%" + kunMatchcode.getValue().toUpperCase() + "%", searchOpId));
		} else if (searchOpId == -1) {
			// do nothing
		} else {
			so.addFilter(new Filter("kunMatchcode", kunMatchcode.getValue(), searchOpId));
		}
	}
	}

	
if (StringUtils.isNotEmpty(kunName1.getValue())) {

		// get the search operator
		Listitem item = sortOperator_kunName1.getSelectedItem();

		if (item != null) {
		     int searchOpId = ((SearchOperators) item.getAttribute("data")).getSearchOperatorId();

		if (searchOpId == Filter.OP_ILIKE) {
			so.addFilter(new Filter("kunName1", "%" + kunName1.getValue().toUpperCase() + "%", searchOpId));
		} else if (searchOpId == -1) {
			// do nothing
		} else {
			so.addFilter(new Filter("kunName1", kunName1.getValue(), searchOpId));
		}
	}
	}


	if (StringUtils.isNotEmpty(kunName2.getValue())) {

		// get the search operator
		Listitem item = sortOperator_kunName2.getSelectedItem();

		if (item != null) {
		     int searchOpId = ((SearchOperators) item.getAttribute("data")).getSearchOperatorId();

			if (searchOpId == Filter.OP_ILIKE) {
			     so.addFilter(new Filter("kunName2", "%" + kunName2.getValue().toUpperCase() + "%", searchOpId));
 		        } else if (searchOpId == -1) {
			      // do nothing
		         } else {
			    so.addFilter(new Filter("kunName2", kunName2.getValue(), searchOpId));
			}
		}
		}

		
if (StringUtils.isNotEmpty(kunOrt.getValue())) {

		// get the search operator
		Listitem item = sortOperator_kunOrt.getSelectedItem();

		if (item != null) {
		     int searchOpId = ((SearchOperators) item.getAttribute("data")).getSearchOperatorId();

			if (searchOpId == Filter.OP_ILIKE) {
			     so.addFilter(new Filter("kunOrt", "%" + kunOrt.getValue().toUpperCase() + "%", searchOpId));
		        } else if (searchOpId == -1) {
			      // do nothing
        		} else {
			     so.addFilter(new Filter("kunOrt", kunOrt.getValue(), searchOpId));
			}
		}
		}

		

if (kunBranche.getSelectedCount() > 0) {

		// check if it the default empty item
		Listitem itemB = kunBranche.getSelectedItem();
		Branche branche = (Branche) itemB.getAttribute("data");

		if (!branche.getBraNr().equalsIgnoreCase("000")) {

			// get the search operator
			Listitem item = sortOperator_kunBranch.getSelectedItem();

				if (item != null) {
					int searchOpId = ((SearchOperators) item.getAttribute("data")).getSearchOperatorId();

					if (searchOpId == Filter.OP_ILIKE) {
						so.addFilter(new Filter("branche", branche, searchOpId));
		                        } else if (searchOpId == -1) {
			                      // do nothing
        		                } else {
						so.addFilter(new Filter("branche", branche, searchOpId));
					}
				}
			}
		}

	if (logger.isDebugEnabled()) {
		List<Filter> lf = so.getFilters();
		for (Filter filter : lf) {
			logger.debug(filter.getProperty().toString() + " / " + filter.getValue().toString());

			if (Filter.OP_ILIKE == filter.getOperator()) {
				logger.debug(filter.getOperator());
				}
			}
		}

		// store the searchObject for reReading
		customerCtrl.setSearchObj(so);

		Listbox listBox = customerCtrl.listBoxCustomer;
		Paging paging = customerCtrl.pagingCustomerList;
		int ps = customerCtrl.pagingCustomerList.getPageSize();

		// set the model to the listbox with the initial resultset  get by the DAO method.
		listBox.setModel(new PagedListWrapper<Kunde>(listBox, paging, 
                                          customerCtrl.getTestService().getSRBySearchObject(so, 0, ps), so));
	}
}


3. The result.

Now we have filtered 929 records that agrees with the search definition. We can now sort the data by pressing a listheader. In our case shown in the images we sort on 『Matchcode』 ascending and descending.

CustomerList 2.jpg CustomerList 3.jpg


Because we do the sort on the database. By changing the sort order the actual page are set back to page 1.


4. Modifying the search definition

If we now call the search mask again we can re-read the stored properties/values from the search object to our mask and modifying the search definition. CustomerSearch reRead.jpg

/**
 * @param event
 * @throws Exception
 */
@SuppressWarnings("unchecked")
public void onCreate$customerSearchWindow(Event event) throws Exception {

	if (logger.isDebugEnabled()) {
		logger.debug("--> " + event.toString());
	}

	doOnCreateCommon(customerSearchWindow, event); // autowire the comps/vars

	if (args.containsKey("customerCtrl")) {
		customerCtrl = (CustomerListCtrl) args.get("customerCtrl");
	} else {
		customerCtrl = null;
	}

	if (args.containsKey("searchObject")) {
		searchObj = (HibernateSearchObject<Kunde>) args.get("searchObject");
	} else {
		searchObj = null;
	}

	// +++++++++++++++++++++++ DropDown ListBox ++++++++++++++++++++++ //
	// set listModel and itemRenderer for the Branch dropdown listboxes
	kunBranche.setModel(new ListModelList(getBrancheService().getAlleBranche()));
	kunBranche.setItemRenderer(new CustomerBrancheListModelItemRenderer());

	// +++++++++++++++++++++++ DropDown ListBox ++++++++++++++++++++++ //
	// set listModel and itemRenderer for the search operator type listboxes
	sortOperator_kunNr.setModel(new ListModelList(new SearchOperators().getAllOperators(), true));
	sortOperator_kunNr.setItemRenderer(new SearchOperatorListModelItemRenderer());
	sortOperator_kunMatchcode.setModel(new ListModelList(new SearchOperators().getAllOperators()));
	sortOperator_kunMatchcode.setItemRenderer(new SearchOperatorListModelItemRenderer());
	sortOperator_kunName1.setModel(new ListModelList(new SearchOperators().getAllOperators()));
	sortOperator_kunName1.setItemRenderer(new SearchOperatorListModelItemRenderer());
	sortOperator_kunName2.setModel(new ListModelList(new SearchOperators().getAllOperators()));
	sortOperator_kunName2.setItemRenderer(new SearchOperatorListModelItemRenderer());
	sortOperator_kunOrt.setModel(new ListModelList(new SearchOperators().getAllOperators()));
	sortOperator_kunOrt.setItemRenderer(new SearchOperatorListModelItemRenderer());
	sortOperator_kunBranch.setModel(new ListModelList(new SearchOperators().getAllOperators()));
	sortOperator_kunBranch.setItemRenderer(new SearchOperatorListModelItemRenderer());

	// ++++ Restore the search mask input definition ++++ //
	// if exists a searchObject than show formerly inputs of filter values
	if (searchObj != null) {

		// get the filters from the searchObject
		List<Filter> ft = searchObj.getFilters();

		for (Filter filter : ft) {

			// restore founded properties
			if (filter.getProperty().equals("kunNr")) {
				restoreOperator(sortOperator_kunNr, filter);
				kunNr.setValue(filter.getValue().toString());
			} else if (filter.getProperty().equals("kunMatchcode")) {
				restoreOperator(sortOperator_kunMatchcode, filter);
				kunMatchcode.setValue(filter.getValue().toString());
			} else if (filter.getProperty().equals("kunName1")) {
				restoreOperator(sortOperator_kunName1, filter);
				kunName1.setValue(filter.getValue().toString());
			} else if (filter.getProperty().equals("kunName2")) {
				restoreOperator(sortOperator_kunName2, filter);
				kunName2.setValue(filter.getValue().toString());
			} else if (filter.getProperty().equals("kunOrt")) {
				restoreOperator(sortOperator_kunOrt, filter);
				kunOrt.setValue(filter.getValue().toString());
			} else if (filter.getProperty().equals("branche")) {
				restoreOperator(sortOperator_kunBranch, filter);
				ListModelList lml = (ListModelList) kunBranche.getModel();
				// get and select the customers branch
				Branche branche = (Branche) filter.getValue();
				kunBranche.setSelectedIndex(lml.indexOf(branche));
			}
		}
	}

	showCustomerSeekDialog();

}

/**
 * Restore the operator sign in the operator listbox by comparing the <br>
 * value of the filter. <br>
 * 
 * @param listbox
 *            Listbox that shows the operator signs.
 * @param filter
 *            Filter that corresponds to the operator listbox.
 */
private void restoreOperator(Listbox listbox, Filter filter) {

	if (filter.getOperator() == Filter.OP_EQUAL) {
		listbox.setSelectedIndex(1);
	} else if (filter.getOperator() == Filter.OP_NOT_EQUAL) {
		listbox.setSelectedIndex(2);
	} else if (filter.getOperator() == Filter.OP_LESS_THAN) {
		listbox.setSelectedIndex(3);
	} else if (filter.getOperator() == Filter.OP_GREATER_THAN) {
		listbox.setSelectedIndex(4);
	} else if (filter.getOperator() == Filter.OP_LESS_OR_EQUAL) {
		listbox.setSelectedIndex(5);
	} else if (filter.getOperator() == Filter.OP_GREATER_OR_EQUAL) {
		listbox.setSelectedIndex(6);
	} else if (filter.getOperator() == Filter.OP_ILIKE) {
		// Delete used '%' signs if the operator is like or iLike
		String str = StringUtils.replaceChars(filter.getValue().toString(), "%", "");
		filter.setValue(str);
		listbox.setSelectedIndex(7);
	}
}


We extend the search criteria about the 『Name 2』 with a equals like 『Vossen』. We became a new resultset about 9 records.

CustomerSearch 2.jpg


5. What happens on the backend side

Remember! All what we have done is creating a PagedListWrapper that holds mainly the listbox, paging component, a initialList or initial SearchResult object and the search object. The backend call when paging or sorting is done by the refreshModel() method.

In this case we need the totalSize of the filtered records only by the initial call. Paging and sorting doesn』t change the totalSize of the resultset because the select and where clause are all times the same.

In our backend DAO there are two methods. Both uses the HibernateSearchProcessor from the Hibernate-Generic_DAO framework to generate a hibernate hql-statement from the search object properties i.e. filters, sorts, entities, values. Absolut great work. Thanks to David.

As you can see the only difference is the result type. Either a List or an object that holds the List and the TotalSize of the query result.

Tip:

By migrating my listbox models to the paged version I have running in the old known 『hibernate session closed』 problem while calling the getter of a related object in the itemRenderer. Formerly these are running well. To solved this you must only add this deeper loaded property to the searchObject with mySearchObject.addFetch(Property String). It』s impressed me much.


6. Conclusion

Paging is an absolut must go criteria for an application that will handle huge sets of data. If we want to allow that the user can sorting the data we need to implement a logic that can remember what is the originally select statement and where clause before modifying the sort order. In this case we can work with a helper class that can hold our search definition. In this article we work with the seperated search object class from the Hibernate-Generic-DAO framework. Additionally this tool can on the backend side generate hql code (Hibernate Query Language) from the stored filter properties. To make the work with this easier we use a few helper classes that we can reuse for every listbox.

But last not least an often missing but interesting point is HOW to integrate in such a paging mechanism the listheaders sort order of the list. In the PagedListWrapper we catch the listheaders events, evaluate them and manipulate our search object with the corresponding listheaders sort order.

Many thanks for reading.

Stephan Gerth

needed Classes/Sources

TestDAOImpl.java

package de.daibutsu.backend.dao.impl;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import com.trg.search.SearchResult;
import com.trg.search.hibernate.HibernateSearchProcessor;

import de.daibutsu.backend.dao.TestDAO;
import de.daibutsu.backend.util.HibernateSearchObject;

/**
 * @author sge
 * 
 */
public class TestDAOImpl extends BasisDAO implements TestDAO {

@Override
public List<?> getBySearchObject(HibernateSearchObject so, int start, int pageSize) {

	SessionFactory sf = getHibernateTemplate().getSessionFactory();
	Session ss = getHibernateTemplate().getSessionFactory().getCurrentSession();

	HibernateSearchProcessor hsp = HibernateSearchProcessor.getInstanceForSessionFactory(sf);
	so.setFirstResult(start);
	so.setMaxResults(pageSize);

	List<?> list = hsp.search(ss, so);

	return list;
	}

@Override
public SearchResult<?> getSRBySearchObject(HibernateSearchObject so, int start, int pageSize) {

	SessionFactory sf = getHibernateTemplate().getSessionFactory();
	Session ss = getHibernateTemplate().getSessionFactory().getCurrentSession();

	HibernateSearchProcessor hsp = HibernateSearchProcessor.getInstanceForSessionFactory(sf);

	so.setFirstResult(start);
	so.setMaxResults(pageSize);

	SearchResult<?> searchresult = hsp.searchAndCount(ss, so);

	return searchresult;
	}
}


BasisDAO.java

package de.daibutsu.backend.dao.impl;

import java.util.Collection;

import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateTemplate;

/**
 * @author bj
 * 
 */
public abstract class BasisDAO<T> {
	private HibernateTemplate hibernateTemplate;

	/**
	 * constructor
	 */
	protected BasisDAO() {
	}

	protected HibernateTemplate getHibernateTemplate() {
		return hibernateTemplate;
	}

	public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
		this.hibernateTemplate = hibernateTemplate;
	}

	protected void initialize(final T proxy) throws DataAccessException {
		hibernateTemplate.initialize(proxy);
	}

	protected void initialize(final Collection<T> proxy) throws DataAccessException {
		hibernateTemplate.initialize(proxy);
	}

	@SuppressWarnings("unchecked")
	protected T merge(T entity) throws DataAccessException {
		return (T) hibernateTemplate.merge(entity);
	}

	protected void persist(T entity) throws DataAccessException {
		hibernateTemplate.persist(entity);
	}

	protected void refresh(T entity) throws DataAccessException {
		hibernateTemplate.refresh(entity);
	}

	protected void save(T entity) throws DataAccessException {
		hibernateTemplate.save(entity);
	}

	// public void saveOrUpdate(T entity) throws DataAccessException {
	// hibernateTemplate.saveOrUpdate(entity);
	// }

	// public void saveOrUpdateAll(Collection<T> entities) throws
	// DataAccessException {
	// for (T entity : entities) {
	// saveOrUpdate(entity);
	// }
	// }

	protected void update(T entity) throws DataAccessException {
		hibernateTemplate.update(entity);
	}

	protected void delete(T entity) throws DataAccessException {
		hibernateTemplate.delete(entity);
	}

	protected void delete(final Collection<T> proxy) throws DataAccessException {
		hibernateTemplate.deleteAll(proxy);
	}
}

PagedListWrapper.java

package org.myfirm.webui.util.pagging;

import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import org.apache.log4j.Logger;
import org.zkoss.lang.Strings;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zkplus.spring.SpringUtil;
import org.zkoss.zul.FieldComparator;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listhead;
import org.zkoss.zul.Listheader;
import org.zkoss.zul.Paging;
import org.zkoss.zul.event.PagingEvent;

import com.trg.search.SearchResult;

import de.daibutsu.backend.service.TestService;
import de.daibutsu.backend.util.HibernateSearchObject;

/**
 * Helper class for easier working to change the sorting of <br>
 * paged records. <br>
 * 
 * <br>
 * All not used Listheaders must me declared as: <br>
 * listheader.setSortAscending(""); <br>
 * listheader.setSortDescending(""); <br>
 * 
 * <br>
 * zkoss 3.6.0 or greater (by using FieldComparator) <br>
 * 
 * @author sge
 * 
 * @param <E>
 */
public class PagedListWrapper<E> extends ListModelList implements Serializable {

	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(PagedListWrapper.class);

	// Service that calls the DAO methods
	private TestService testService;

	// param. The listbox component
	protected Listbox _listBox;

	// param. The listboxes paging component
	protected Paging _paging;

	// param. Initial list if needed
	private List<E> _list;

	// param. Initial SearchResult if needed
	private SearchResult<E> _searchResult;

	// param. The SearchObject
	private HibernateSearchObject<E> _hibernateSearchObject;

	// paging start row
	private int _start;

	// count records a page have to fetch
	private int _pageSize;

	// count total records queried (without paging)
	private int _totalCount;

	// not used yet. so it's init to 'true'.
	private boolean _supportPaging = true;

	// not used yet. so it's init to
	private boolean _supportFilter = true;

	/**
	 * Constructor. <br>
	 * with an initial list.
	 * 
	 * @param listBox
	 *            Overhanded listBox. <br>
	 * @param paging
	 *            Overhanded Paging component. <br>
	 * @param initialList
	 *            Overhanded List with initial data. <br>
	 * @param searchObj
	 *            Overhanded SearchObject. <br>
	 */
	public PagedListWrapper(Listbox listBox, Paging paging, List initialList, HibernateSearchObject<E> searchObj) {

		super(initialList);

		this._listBox = listBox;
		this._paging = paging;
		this._pageSize = _paging.getPageSize();
		this._hibernateSearchObject = searchObj;

		set_list(initialList);

		setListeners();
	}

	/**
	 * Constructor. <br>
	 * with an initial SearchResult for getting the totalRecordCount and the
	 * list. <br>
	 * 
	 * @param listBox
	 *            Overhanded listBox. <br>
	 * @param paging
	 *            Overhanded Paging component. <br>
	 * @param searchResult
	 *            Overhanded SearchResult Object. <br>
	 * @param searchObj
	 *            Overhanded SearchObject. <br>
	 */
	public PagedListWrapper(Listbox listBox, Paging paging, SearchResult searchResult, HibernateSearchObject searchObj) {

		super(searchResult.getResult());

		this._listBox = listBox;
		this._paging = paging;
		this._paging.setTotalSize(searchResult.getTotalCount());
		this._pageSize = _paging.getPageSize();
		this._searchResult = searchResult;
		this._hibernateSearchObject = searchObj;

		set_list(searchResult.getResult());
		set_totalCount(searchResult.getTotalCount());

		setListeners();
	}

	/**
	 * Sets the listeners. <br>
	 * <br>
	 * 1. "onPaging" for the paging component. <br>
	 * 2. "onSort" for all listheaders that have a sortDirection declared. <br>
	 */
	private void setListeners() {

		// Add 'onPaging' listener to the paging component
		_paging.addEventListener("onPaging", new OnPagingEventListener());

		/*
		 * Add 'onSort' listeners to the used listheader components. All not
		 * used Listheaders must me declared as:
		 * listheader.setSortAscending(""); <br>
		 * listheader.setSortDescending(""); <br>
		 */
		Listhead listhead = _listBox.getListhead();
		List list = listhead.getChildren();

		for (Object object : list) {
			if (object instanceof Listheader) {
				Listheader lheader = (Listheader) object;

				if (lheader.getSortAscending() != null || lheader.getSortDescending() != null) {

					if (logger.isDebugEnabled()) {
						logger.debug("--> : " + lheader.getId());
					}
					lheader.addEventListener("onSort", new OnSortEventListener());
				}
			}
		}

	}

	/**
	 * "onPaging" eventlistener for the paging component. <br>
	 * <br>
	 * Calculates the next page by currentPage and pageSize values. <br>
	 * Calls the methode for refreshing the data with the new rowStart and
	 * pageSize. <br>
	 */
	public final class OnPagingEventListener implements EventListener {
		@Override
		public void onEvent(Event event) throws Exception {

			PagingEvent pe = (PagingEvent) event;
			int pageNo = pe.getActivePage();
			int start = pageNo * _pageSize;

			if (logger.isDebugEnabled()) {
				logger.debug("--> : " + start + "/" + _pageSize);
			}

			// refresh the list
			refreshModel(get_hibernateSearchObject(), start, _pageSize);
		}
	}

	/**
	 * "onSort" eventlistener for the listheader components. <br>
	 * <br>
	 * Checks wich listheader is clicked and checks which orderDirection must be
	 * set. <br>
	 * 
	 * Calls the methode for refreshing the data with the new ordering. and the
	 * remembered rowStart and pageSize. <br>
	 */
	public final class OnSortEventListener implements EventListener {
		@Override
		public void onEvent(Event event) throws Exception {
			final Listheader lh = (Listheader) event.getTarget();
			final String sortDirection = lh.getSortDirection();

			if ("ascending".equals(sortDirection)) {
				final Comparator cmpr = lh.getSortDescending();
				if (cmpr instanceof FieldComparator) {
					String orderBy = ((FieldComparator) cmpr).getOrderBy();
					orderBy = orderBy.replace("DESC", "").trim();

					// update SearchObject with orderBy
					get_hibernateSearchObject().clearSorts();
					get_hibernateSearchObject().addSort(orderBy, true);
				}
			} else if ("descending".equals(sortDirection) || "natural".equals(sortDirection) || Strings.isBlank(sortDirection)) {
				final Comparator cmpr = lh.getSortAscending();
				if (cmpr instanceof FieldComparator) {
					String orderBy = ((FieldComparator) cmpr).getOrderBy();
					orderBy = orderBy.replace("ASC", "").trim();

					// update SearchObject with orderBy
					get_hibernateSearchObject().clearSorts();
					get_hibernateSearchObject().addSort(orderBy, false);
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("--> : " + lh.getId() + "/" + sortDirection);
				logger.debug("--> added  getSorts() : " + get_hibernateSearchObject().getSorts().toString());
			}

			if (is_supportPagging()) {
				// refresh the list
				refreshModel(get_hibernateSearchObject(), 0, _pageSize);
				_paging.setActivePage(0);
			}
		}
	}

	/**
	 * Refreshes the list by calling the DAO methode with the modified search
	 * object. <br>
	 * 
	 * @param so
	 *            SearchObject, holds the entity and properties to search. <br>
	 * @param start
	 *            Row to start. <br>
	 * @param pageSize
	 *            Count rows to fetch. <br>
	 */
	private void refreshModel(HibernateSearchObject<E> so, int start, int pageSize) {

		// clear old data
		get_list().clear();

		List list = getTestService().getBySearchObject(so, start, pageSize);
		get_list().addAll((Collection) list);
		_listBox.setModel(new ListModelList(get_list()));

	}

	// +++++++++++++++++++++++++++++++++++++++++++++++++ //
	// ++++++++++++++++ Setter/Getter ++++++++++++++++++ //
	// +++++++++++++++++++++++++++++++++++++++++++++++++ //

	public void set_supportPagging(boolean _supportPagging) {
		this._supportPaging = _supportPagging;
	}

	public boolean is_supportPagging() {
		return _supportPaging;
	}

	public void set_supportFilter(boolean _supportFilter) {
		this._supportFilter = _supportFilter;
	}

	public boolean is_supportFilter() {
		return _supportFilter;
	}

	public void set_hibernateSearchObject(HibernateSearchObject<E> hibernateSearchObject) {
		this._hibernateSearchObject = hibernateSearchObject;
	}

	public HibernateSearchObject<E> get_hibernateSearchObject() {
		return _hibernateSearchObject;
	}

	public void setTestService(TestService testService) {
		this.testService = testService;
	}

	public TestService getTestService() {
		if (testService == null) {
			testService = (TestService) SpringUtil.getBean("testService");
			setTestService(testService);
		}
		return testService;
	}

	public void set_totalCount(int _totalCount) {
		this._totalCount = _totalCount;
	}

	public int get_totalCount() {
		return _totalCount;
	}

	public void set_list(List<E> _list) {
		this._list = _list;
	}

	public List<E> get_list() {
		return _list;
	}

}


SearchOperators.java

package org.myfirm.webui.util.searching;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.trg.search.Filter;

/**
 * This class represents only a few  types of search operators <br>
 * corresponding to the com.trg.search.Search.java class. <br>
 * from the Hibernate-Generic-DAO framework. <br>
 * <br>
 * The domain model have no corresponding table in a database and has a fixed
 * length of records that should see as the search operators of what to search. <br>
 * It's used only for showing the several operators in a dropdown list. <br>
 * <br>
 * Int | sign | search operator <br>
 * ------------------------------------------<br>
 * -1 | | no operator (like empty for reset) <br>
 * 0 | = | equals <br>
 * 1 | # | not equal <br>
 * 2 | < | less than <br>
 * 3 | > | greater than <br>
 * 4 | <= | less or equal <br>
 * 5 | >= | greater or equal <br>
 * 7 | ~ | ilike <br>
 * 
 * @author [email protected]
 */
public class SearchOperators implements Serializable {

	private static final long serialVersionUID = 1L;

	private int searchOperatorId;
	private String searchOperatorSign;
	private String searchOperatorName;

	public void setSearchOperatorId(int searchOperatorId) {
		this.searchOperatorId = searchOperatorId;
	}

	public int getSearchOperatorId() {
		return searchOperatorId;
	}

	public void setSearchOperatorSign(String searchOperatorSign) {
		this.searchOperatorSign = searchOperatorSign;
	}

	public String getSearchOperatorSign() {
		return searchOperatorSign;
	}

	public void setSearchOperatorName(String searchOperatorName) {
		this.searchOperatorName = searchOperatorName;
	}

	public String getSearchOperatorName() {
		return searchOperatorName;
	}

	public SearchOperators() {
	}

	public SearchOperators(int searchOperatorId, String searchOperatorSign, String searchOperatorName) {
		this.searchOperatorId = searchOperatorId;
		this.searchOperatorSign = searchOperatorSign;
		this.searchOperatorName = searchOperatorName;

	}

	public List<SearchOperators> getAllOperators() {

		List<SearchOperators> result = new ArrayList<SearchOperators>();

		// list position 0
		result.add(new SearchOperators(-1, "", "no operator"));
		// list position 1
		result.add(new SearchOperators(Filter.OP_EQUAL, "=", "equals"));
		// list position 2
		result.add(new SearchOperators(Filter.OP_NOT_EQUAL, "#", "not equal"));
		// list position 3
		result.add(new SearchOperators(Filter.OP_LESS_THAN, "<", "less than"));
		// list position 4
		result.add(new SearchOperators(Filter.OP_GREATER_THAN, ">", "greater than"));
		// list position 5
		result.add(new SearchOperators(Filter.OP_LESS_OR_EQUAL, "<=", "less or equal"));
		// list position 6
		result.add(new SearchOperators(Filter.OP_GREATER_OR_EQUAL, ">=", "greater or equal"));
		// list position 7
		result.add(new SearchOperators(Filter.OP_ILIKE, "~", "ilike"));

		return result;
	}

	@Override
	public int hashCode() {
		return getSearchOperatorId();
	}

	public boolean equals(SearchOperators searchOperators) {
		return getSearchOperatorId() == searchOperators.getSearchOperatorId();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}

		if (obj instanceof SearchOperators) {
			SearchOperators searchOperators = (SearchOperators) obj;
			return equals(searchOperators);
		}

		return false;
	}

}

SearchOperatorListModelItemRenderer.java

package org.myfirm.webui.util.searching;

import org.apache.log4j.Logger;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;

public class SearchOperatorListModelItemRenderer implements ListitemRenderer {

	private static Logger logger = Logger.getLogger(SearchOperatorListModelItemRenderer.class);

	@Override
	public void render(Listitem item, Object data) throws Exception {

		SearchOperators searchOp = (SearchOperators) data;

		if (logger.isDebugEnabled()) {
			logger.debug("--> " + searchOp.getSearchOperatorName());
		}

		Listcell lc = new Listcell(searchOp.getSearchOperatorSign());
		lc.setParent(item);

		item.setAttribute("data", data);
	}

}


HibernateSearchObject.java

package de.daibutsu.backend.util;

import com.trg.search.Search;

/**
 * SearchObject depending on the Search Object from the Hibernate-Generic-DAO
 * framework. <br>
 * 
 * @see http://code.google.com/p/hibernate-generic-dao/ <br>
 *      Many thanks to David Wolvert.
 * 
 * @author sge
 * 
 * @param <E>
 */
public class HibernateSearchObject<E> extends Search {

	private static final long serialVersionUID = 1L;

	public HibernateSearchObject(Class<E> entityClass) {
		super(entityClass);
	}
}


CustomerSearchDialog.zul

<?xml version="1.0" encoding="UTF-8" ?>
<zk xmlns="http://www.zkoss.org/2005/zul"
	xmlns:h="http://www.w3.org/1999/xhtml"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.zkoss.org/2005/zul http://www.zkoss.org/2005/zul/zul.xsd">
	<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>

	<window id="customerSearchWindow"
		title="${c:l('customerSearchWindow.title')}" border="none"
		use="org.myfirm.webui.customer.CustomerSearchCtrl" closable="true"
		width="400px" height="280px" sizable="true">


		<borderlayout>
			<north></north>
			<center flex="true">

				<panel>
					<panelchildren style="padding: 5px">
						<grid fixedLayout="true" style="border:0px"
							width="auto">
							<columns>
								<column width="130px" />
								<column width="50px" />
								<column width="100%" />
							</columns>
							<rows>
								<row>
									<label
										id="label_CustomerSearch_kunNr"
										value="${c:l('label_CustomerSearch_kunNr.value')}" />
									<listbox id="sortOperator_kunNr"
										width="43px" mold="select" rows="1" />
									<textbox id="kunNr" width="150px" />
								</row>
								<row>
									<separator bar="true"></separator>
									<separator bar="true"></separator>
								</row>
								<row>
									<label
										id="label_CustomerSearch_kunMatchcode"
										value="${c:l('label_CustomerSearch_kunMatchcode.value')}" />
									<listbox
										id="sortOperator_kunMatchcode" width="43px" mold="select"
										rows="1" />
									<textbox id="kunMatchcode"
										width="150px" />
								</row>
								<row>
									<separator bar="true"></separator>
									<separator bar="true"></separator>
								</row>
								<row>
									<label
										id="label_CustomerSearch_kunName1"
										value="${c:l('label_CustomerSearch_kunName1.value')}" />
									<listbox id="sortOperator_kunName1"
										width="43px" mold="select" rows="1" />
									<textbox id="kunName1" width="99%" />
								</row>
								<row>
									<label
										id="label_CustomerSearch_kunName2"
										value="${c:l('label_CustomerSearch_kunName2.value')}" />
									<listbox id="sortOperator_kunName2"
										width="43px" mold="select" rows="1" />
									<textbox id="kunName2" width="99%" />
								</row>
								<row>
									<label
										id="label_CustomerSearch_kunOrt"
										value="${c:l('label_CustomerSearch_kunOrt.value')}" />
									<listbox id="sortOperator_kunOrt"
										width="43px" mold="select" rows="1" />
									<textbox id="kunOrt" width="99%" />
								</row>
								<row>
									<label
										id="label_CustomerSearch_kunBranche"
										value="${c:l('label_CustomerSearch_kunBranche.value')}" />
									<listbox id="sortOperator_kunBranch"
										width="43px" mold="select" rows="1" />
									<listbox id="kunBranche"
										width="100%" mold="select" rows="1" />
								</row>
							</rows>
						</grid>
					</panelchildren>
				</panel>
			</center>

			<south border="none" margins="1,0,0,0" size="26px"
				splittable="false">
				<div id="divSouthCustomer" align="right">
					<grid>
						<rows>
							<row valign="middle">
								<hbox align="end">
									<button id="btnSearch" height="20"
										label="${c:l('btnSearch.label')}"
										tooltiptext="${c:l('btnSearch.tooltiptext')}" />
									<button id="btnClose" height="20"
										label="${c:l('btnClose.label')}"
										tooltiptext="${c:l('btnClose.tooltiptext')}" />
								</hbox>
							</row>
						</rows>
					</grid>
				</div>
			</south>

		</borderlayout>
	</window>
</zk>




Copyright © Stephan Gerth. This article is licensed under GNU Free Documentation License.