/* * 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 onConfigure() { super.onConfigure(); 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(); } /** * 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(org.apache.wicket.markup.html.list.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).intern(); } 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(org.apache.wicket.markup.html.list.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) { String pageIndex = String.valueOf(page + 1).intern(); Map<String, String> vars = new MicroMap<String, String>("page", pageIndex); tag.put("title", PagingNavigation.this.getString(RES, Model.ofMap(vars))); } } }