/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.wicket.markup.html.navigation.paging;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.list.Loop;
import org.apache.wicket.markup.html.list.LoopItem;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.collections.MicroMap;
/**
* A navigation for a PageableListView that holds links to other pages of the PageableListView.
* <p>
* For each row (one page of the list of pages) a {@link PagingNavigationLink}will be added that
* contains a {@link Label} with the page number of that link (1..n).
*
* <pre>
*
* <td wicket:id="navigation">
* <a wicket:id="pageLink" href="SearchCDPage.html">
* <span wicket:id="pageNumber">1</span>
* </a>
* </td>
*
* </pre>
*
* thus renders like:
*
* <pre>
* 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
* </pre>
*
* </p>
* <p>
* Override method populateItem to customize the rendering of the navigation. For instance:
*
* <pre>
* protected void populateItem(LoopItem loopItem)
* {
* final int page = loopItem.getIteration();
* final PagingNavigationLink link = new PagingNavigationLink("pageLink", pageableListView, page);
* if (page > 0)
* {
* loopItem.add(new Label("separator", "|"));
* }
* else
* {
* loopItem.add(new Label("separator", ""));
* }
* link.add(new Label("pageNumber", String.valueOf(page + 1)));
* link.add(new Label("pageLabel", "page"));
* loopItem.add(link);
* }
* </pre>
*
* With:
*
* <pre>
* <span wicket:id="navigation">
* <span wicket:id="separator"></span>
* <a wicket:id="pageLink" href="#">
* <span wicket:id="pageLabel"></span><span wicket:id="pageNumber"></span>
* </a>
* </span>
* </pre>
*
* renders like:
*
* <pre>
* page1 | page2 | page3 | page4 | page5 | page6 | page7 | page8 | page9
* </pre>
*
* </p>
* Assuming a PageableListView with 1000 entries and not more than 10 lines shall be printed per
* page, the navigation bar would have 100 entries. Because this is not feasible PagingNavigation's
* navigation bar is pageable as well.
* <p>
* The page links displayed are automatically adjusted based on the number of page links to be
* displayed and a margin. The margin makes sure that the page link pointing to the current page is
* not at the left or right end of the page links currently printed and thus providing a better user
* experience.
* <p>
* Use setMargin() and setViewSize() to adjust the navigation's bar view size and margin.
* <p>
* Please
*
* @see PagingNavigator for a ready made component which already includes links to the first,
* previous, next and last page.
*
* @author Jonathan Locke
* @author Eelco Hillenius
* @author Juergen Donnerstag
*/
public class PagingNavigation extends Loop
{
private static final long serialVersionUID = 1L;
/** The PageableListView this navigation is navigating. */
protected IPageable pageable;
/** The label provider for the text that the links should be displaying. */
protected IPagingLabelProvider labelProvider;
/** Offset for the Loop */
private long startIndex;
/**
* Number of links on the left and/or right to keep the current page link somewhere near the
* middle.
*/
private long margin = -1;
/** Default separator between page numbers. Null: no separator. */
private String separator = null;
/**
* The maximum number of page links to show.
*/
private int viewSize = 10;
/**
* Constructor.
*
* @param id
* See Component
* @param pageable
* The underlying pageable component to navigate
*/
public PagingNavigation(final String id, final IPageable pageable)
{
this(id, pageable, null);
}
/**
* Constructor.
*
* @param id
* See Component
* @param pageable
* The underlying pageable component to navigate
* @param labelProvider
* The label provider for the text that the links should be displaying.
*/
public PagingNavigation(final String id, final IPageable pageable,
final IPagingLabelProvider labelProvider)
{
super(id, null);
this.pageable = pageable;
this.labelProvider = labelProvider;
startIndex = 0;
}
/**
* Gets the margin, default value is half the view size, unless explicitly set.
*
* @return the margin
*/
public long getMargin()
{
if (margin == -1 && viewSize != 0)
{
return viewSize / 2;
}
return margin;
}
/**
* Gets the seperator.
*
* @return the seperator
*/
public String getSeparator()
{
return separator;
}
/**
* Gets the view size (is fixed by user).
*
* @return view size
*/
public int getViewSize()
{
return viewSize;
}
/**
* view size of the navigation bar.
*
* @param size
*/
public void setViewSize(final int size)
{
viewSize = size;
}
/**
* Sets the margin.
*
* @param margin
* the margin
*/
public void setMargin(final int margin)
{
this.margin = margin;
}
/**
* Sets the seperator. Null meaning, no separator at all.
*
* @param separator
* the seperator
*/
public void setSeparator(final String separator)
{
this.separator = separator;
}
@Override
protected void onBeforeRender()
{
setDefaultModel(new Model<Integer>(
(int)Math.max(Integer.MAX_VALUE, pageable.getPageCount())));
// PagingNavigation itself (as well as the PageableListView)
// may have pages.
// The index of the first page link depends on the PageableListView's
// page currently printed.
setStartIndex();
super.onBeforeRender();
}
/**
* Allow subclasses replacing populateItem to calculate the current page number
*
* @return start index
*/
protected final long getStartIndex()
{
return startIndex;
}
/**
* Populate the current cell with a page link (PagingNavigationLink) enclosing the page number
* the link is pointing to. Subclasses may provide there own implementation adding more
* sophisticated page links.
*
* @see org.apache.wicket.markup.html.list.Loop#populateItem(Loop.LoopItem)
*/
@Override
protected void populateItem(final LoopItem loopItem)
{
// Get the index of page this link shall point to
final long pageIndex = getStartIndex() + loopItem.getIndex();
// Add a page link pointing to the page
final AbstractLink link = newPagingNavigationLink("pageLink", pageable, pageIndex);
link.add(new TitleAppender(pageIndex));
loopItem.add(link);
// Add a page number label to the list which is enclosed by the link
String label = "";
if (labelProvider != null)
{
label = labelProvider.getPageLabel(pageIndex);
}
else
{
label = String.valueOf(pageIndex + 1);
}
link.add(new Label("pageNumber", label));
}
/**
* Factory method for creating page number links.
*
* @param id
* the component id.
* @param pageable
* the pageable for the link
* @param pageIndex
* the page index the link points to
* @return the page navigation link.
*/
protected AbstractLink newPagingNavigationLink(String id, IPageable pageable, long pageIndex)
{
return new PagingNavigationLink<Void>(id, pageable, pageIndex);
}
/**
* Renders the page link. Add the separator if not the last page link
*
* @see Loop#renderItem(Loop.LoopItem)
*/
@Override
protected void renderItem(final LoopItem loopItem)
{
// Call default implementation
super.renderItem(loopItem);
// Add separator if not last page
if (separator != null && (loopItem.getIndex() != getIterations() - 1))
{
getResponse().write(separator);
}
}
/**
* Get the first page link to render. Adjust the first page link based on the current
* PageableListView page displayed.
*/
private void setStartIndex()
{
// Which startIndex are we currently using
long firstListItem = startIndex;
// How many page links shall be displayed
int viewSize = (int)Math.min(getViewSize(), pageable.getPageCount());
long margin = getMargin();
// What is the PageableListView's page index to be displayed
long currentPage = pageable.getCurrentPage();
// Make sure the current page link index is within the current
// window taking the left and right margin into account
if (currentPage < (firstListItem + margin))
{
firstListItem = currentPage - margin;
}
else if ((currentPage >= (firstListItem + viewSize - margin)))
{
firstListItem = (currentPage + margin + 1) - viewSize;
}
// Make sure the first index is >= 0 and the last index is <=
// than the last page link index.
if ((firstListItem + viewSize) >= pageable.getPageCount())
{
firstListItem = pageable.getPageCount() - viewSize;
}
if (firstListItem < 0)
{
firstListItem = 0;
}
if ((viewSize != getIterations()) || (startIndex != firstListItem))
{
modelChanging();
// Tell the ListView what the new start index shall be
addStateChange();
startIndex = firstListItem;
setIterations((int)Math.min(viewSize, pageable.getPageCount()));
modelChanged();
// force all children to be re-rendered
removeAll();
}
}
/**
* Set the number of iterations.
*
* @param i
* the number of iterations
*/
private void setIterations(int i)
{
setDefaultModelObject(i);
}
/**
* Appends title attribute to navigation links
*
* @author igor.vaynberg
*/
private final class TitleAppender extends Behavior
{
private static final long serialVersionUID = 1L;
/** resource key for the message */
private static final String RES = "PagingNavigation.page";
/** page number */
private final long page;
/**
* Constructor
*
* @param page
* page number to use as the ${page} var
*/
public TitleAppender(long page)
{
this.page = page;
}
/** {@inheritDoc} */
@Override
public void onComponentTag(Component component, ComponentTag tag)
{
Map<String, String> vars = new MicroMap<String, String>("page",
String.valueOf(page + 1));
tag.put("title", PagingNavigation.this.getString(RES, Model.ofMap(vars)));
}
}
}