blob: f79f71edb0b9ddd489123172ce6ee81c1700289f [file] [log] [blame]
/*
* $Id$
* $Revision$
* $Date$
*
* ====================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package wicket.extensions.markup.html.repeater.pageable;
import java.util.Iterator;
import java.util.NoSuchElementException;
import wicket.extensions.markup.html.repeater.refreshing.RefreshingView;
import wicket.markup.html.navigation.paging.IPageable;
import wicket.model.IModel;
import wicket.version.undo.Change;
/**
* An abstract repeater view that provides paging functionality to its
* subclasses.
* <p>
* The view is populated by overriding the
* <code>getItemModels(int offset, int count)</code> method and providing an
* iterator that returns models for items in the current page. The
* AbstractPageableView builds the items that will be rendered by looping over
* the models and calling the
* <code>newItem(String id, int index, IModel model)</code> to generate the
* child item container followed by <code>populateItem(Component item)</code>
* to let the user populate the newly created item container with with custom
* components.
* </p>
*
* @see wicket.extensions.markup.html.repeater.refreshing.RefreshingView
* @see wicket.markup.html.navigation.paging.IPageable
*
* @author Igor Vaynberg (ivaynberg)
*
*/
public abstract class AbstractPageableView extends RefreshingView implements IPageable
{
/**
* Keeps track of the number of items we show per page. The default is
* Integer.MAX_VALUE which effectively disables paging.
*/
private int itemsPerPage = Integer.MAX_VALUE;
/**
* Keeps track of the current page number.
*/
private int currentPage;
/**
* <code>cachedItemCount</code> is used to cache the call to
* <code>internalGetItemCount()</code> for the duration of the request
* because that call can potentially be expensive ( a select count query )
* and so we do not want to execute it multiple times.
*/
private int cachedItemCount;
/** @see wicket.Component#Component(String, IModel) */
public AbstractPageableView(String id, IModel model)
{
super(id, model);
clearCachedItemCount();
}
/** @see wicket.Component#Component(String) */
public AbstractPageableView(String id)
{
super(id);
clearCachedItemCount();
}
/**
* This method retrieves the subset of models for items in the current page
* and allows RefreshingView to generate items.
*
* @return iterator over models for items in the current page
*/
protected Iterator getItemModels()
{
int offset = getViewOffset();
int size = getViewSize();
Iterator models = getItemModels(offset, size);
models = new CappedIteratorAdapter(models, size);
return models;
}
protected void internalOnEndRequest()
{
super.internalOnEndRequest();
clearCachedItemCount();
}
/**
* Returns an iterator over models for items in the current page
*
* @param offset
* index of first item in this page
* @param size
* number of items that will be showin in the current page
* @return an iterator over models for items in the current page
*/
protected abstract Iterator getItemModels(int offset, int size);
// /////////////////////////////////////////////////////////////////////////
// ITEM COUNT CACHE
// /////////////////////////////////////////////////////////////////////////
private void clearCachedItemCount()
{
cachedItemCount = -1;
}
private void setCachedItemCount(int itemCount)
{
cachedItemCount = itemCount;
}
private int getCachedItemCount()
{
if (cachedItemCount < 0)
{
throw new IllegalStateException("getItemCountCache() called when cache was not set");
}
return cachedItemCount;
}
private boolean isItemCountCached()
{
return cachedItemCount >= 0;
}
// /////////////////////////////////////////////////////////////////////////
// PAGING
// /////////////////////////////////////////////////////////////////////////
/**
* @return maximum number of items that will be shown per page
*/
protected final int internalGetRowsPerPage()
{
return itemsPerPage;
}
/**
* Sets the maximum number of items to show per page. The current page will
* also be set to zero
*
* @param items
*/
protected final void internalSetRowsPerPage(int items)
{
if (items < 1)
{
throw new IllegalArgumentException("Argument [itemsPerPage] cannot be less then 1");
}
if (itemsPerPage != items)
{
if (isVersioned())
{
addStateChange(new Change()
{
private static final long serialVersionUID = 1L;
final int old = itemsPerPage;
public void undo()
{
itemsPerPage = old;
}
public String toString()
{
return "ItemsPerPageChange[component: " + getPath() + ", itemsPerPage: "
+ old + "]";
}
});
}
}
itemsPerPage = items;
// because items per page can effect the total number of pages we always
// reset the current page back to zero
setCurrentPage(0);
}
/**
* @return total item count
*/
protected abstract int internalGetItemCount();
/**
* @return total item count
*/
public final int getRowCount()
{
if (!isVisibleInHierarchy())
{
return 0;
}
if (isItemCountCached())
{
return getCachedItemCount();
}
int count = internalGetItemCount();
setCachedItemCount(count);
return count;
}
/**
* @see wicket.markup.html.navigation.paging.IPageable#getCurrentPage()
*/
public final int getCurrentPage()
{
int page = currentPage;
/*
* trim current page if its out of bounds this can happen if items are
* added/deleted between requests
*/
if (page >= getPageCount())
{
page = Math.max(page - 1, 0);
setCurrentPage(page);
return page;
}
return page;
}
/**
* @see wicket.markup.html.navigation.paging.IPageable#setCurrentPage(int)
*/
public final void setCurrentPage(int page)
{
if (page < 0 || (page >= getPageCount() && getPageCount() > 0))
{
throw new IndexOutOfBoundsException("argument [page]=" + page + ", must be 0<=page<"
+ getPageCount());
}
if (currentPage != page)
{
if (isVersioned()) {
addStateChange(new Change()
{
private static final long serialVersionUID = 1L;
private final int old = currentPage;
public void undo()
{
currentPage = old;
}
public String toString()
{
return "CurrentPageChange[component: " + getPath() + ", currentPage: " + old
+ "]";
}
});
}
}
currentPage = page;
}
/**
* @see wicket.markup.html.navigation.paging.IPageable#getPageCount()
*/
public final int getPageCount()
{
int total = getRowCount();
int page = internalGetRowsPerPage();
int count = total / page;
if (page * count < total)
{
count++;
}
return count;
}
/**
* @return the index of the first visible item
*/
protected int getViewOffset()
{
return getCurrentPage() * internalGetRowsPerPage();
}
/**
* @return the number of items visible
*/
protected int getViewSize()
{
return Math.min(internalGetRowsPerPage(), getRowCount() - getViewOffset());
}
// /////////////////////////////////////////////////////////////////////////
// HELPER CLASSES
// /////////////////////////////////////////////////////////////////////////
/**
* Iterator adapter that makes sure only the specified max number of items
* can be accessed from its delegate.
*/
private static class CappedIteratorAdapter implements Iterator
{
private int max;
private int index;
private Iterator delegate;
/**
* Constructor
*
* @param delegate
* delegate iterator
* @param max
* maximum number of items that can be accessed.
*/
public CappedIteratorAdapter(Iterator delegate, int max)
{
this.delegate = delegate;
this.max = max;
}
/**
* @see java.util.Iterator#remove()
*/
public void remove()
{
throw new UnsupportedOperationException();
}
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext()
{
return (index < max) && delegate.hasNext();
}
/**
* @see java.util.Iterator#next()
*/
public Object next()
{
if (index >= max)
{
throw new NoSuchElementException();
}
index++;
return delegate.next();
}
};
}