001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.navigation.paging; 018 019import java.util.Map; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.behavior.Behavior; 023import org.apache.wicket.markup.ComponentTag; 024import org.apache.wicket.markup.html.basic.Label; 025import org.apache.wicket.markup.html.link.AbstractLink; 026import org.apache.wicket.markup.html.list.Loop; 027import org.apache.wicket.markup.html.list.LoopItem; 028import org.apache.wicket.model.Model; 029import org.apache.wicket.util.collections.MicroMap; 030 031/** 032 * A navigation for a PageableListView that holds links to other pages of the PageableListView. 033 * <p> 034 * For each row (one page of the list of pages) a {@link PagingNavigationLink}will be added that 035 * contains a {@link Label} with the page number of that link (1..n). 036 * 037 * <pre> 038 * 039 * <td wicket:id="navigation"> 040 * <a wicket:id="pageLink" href="SearchCDPage.html"> 041 * <span wicket:id="pageNumber">1</span> 042 * </a> 043 * </td> 044 * 045 * </pre> 046 * 047 * thus renders like: 048 * 049 * <pre> 050 * 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 051 * </pre> 052 * 053 * </p> 054 * <p> 055 * Override method populateItem to customize the rendering of the navigation. For instance: 056 * 057 * <pre> 058 * protected void populateItem(LoopItem loopItem) 059 * { 060 * final int page = loopItem.getIteration(); 061 * final PagingNavigationLink link = new PagingNavigationLink("pageLink", pageableListView, page); 062 * if (page > 0) 063 * { 064 * loopItem.add(new Label("separator", "|")); 065 * } 066 * else 067 * { 068 * loopItem.add(new Label("separator", "")); 069 * } 070 * link.add(new Label("pageNumber", String.valueOf(page + 1))); 071 * link.add(new Label("pageLabel", "page")); 072 * loopItem.add(link); 073 * } 074 * </pre> 075 * 076 * With: 077 * 078 * <pre> 079 * <span wicket:id="navigation"> 080 * <span wicket:id="separator"></span> 081 * <a wicket:id="pageLink" href="#"> 082 * <span wicket:id="pageLabel"></span><span wicket:id="pageNumber"></span> 083 * </a> 084 * </span> 085 * </pre> 086 * 087 * renders like: 088 * 089 * <pre> 090 * page1 | page2 | page3 | page4 | page5 | page6 | page7 | page8 | page9 091 * </pre> 092 * 093 * </p> 094 * Assuming a PageableListView with 1000 entries and not more than 10 lines shall be printed per 095 * page, the navigation bar would have 100 entries. Because this is not feasible PagingNavigation's 096 * navigation bar is pageable as well. 097 * <p> 098 * The page links displayed are automatically adjusted based on the number of page links to be 099 * displayed and a margin. The margin makes sure that the page link pointing to the current page is 100 * not at the left or right end of the page links currently printed and thus providing a better user 101 * experience. 102 * <p> 103 * Use setMargin() and setViewSize() to adjust the navigation's bar view size and margin. 104 * <p> 105 * Please 106 * 107 * @see PagingNavigator for a ready made component which already includes links to the first, 108 * previous, next and last page. 109 * 110 * @author Jonathan Locke 111 * @author Eelco Hillenius 112 * @author Juergen Donnerstag 113 */ 114public class PagingNavigation extends Loop 115{ 116 private static final long serialVersionUID = 1L; 117 118 /** The PageableListView this navigation is navigating. */ 119 protected IPageable pageable; 120 121 /** The label provider for the text that the links should be displaying. */ 122 protected IPagingLabelProvider labelProvider; 123 124 /** Offset for the Loop */ 125 private long startIndex; 126 127 /** 128 * Number of links on the left and/or right to keep the current page link somewhere near the 129 * middle. 130 */ 131 private long margin = -1; 132 133 /** Default separator between page numbers. Null: no separator. */ 134 private String separator = null; 135 136 /** 137 * The maximum number of page links to show. 138 */ 139 private int viewSize = 10; 140 141 142 /** 143 * Constructor. 144 * 145 * @param id 146 * See Component 147 * @param pageable 148 * The underlying pageable component to navigate 149 */ 150 public PagingNavigation(final String id, final IPageable pageable) 151 { 152 this(id, pageable, null); 153 } 154 155 /** 156 * Constructor. 157 * 158 * @param id 159 * See Component 160 * @param pageable 161 * The underlying pageable component to navigate 162 * @param labelProvider 163 * The label provider for the text that the links should be displaying. 164 */ 165 public PagingNavigation(final String id, final IPageable pageable, 166 final IPagingLabelProvider labelProvider) 167 { 168 super(id, null); 169 this.pageable = pageable; 170 this.labelProvider = labelProvider; 171 startIndex = 0; 172 } 173 174 /** 175 * Gets the margin, default value is half the view size, unless explicitly set. 176 * 177 * @return the margin 178 */ 179 public long getMargin() 180 { 181 if (margin == -1 && viewSize != 0) 182 { 183 return viewSize / 2; 184 } 185 return margin; 186 } 187 188 /** 189 * Gets the seperator. 190 * 191 * @return the seperator 192 */ 193 public String getSeparator() 194 { 195 return separator; 196 } 197 198 /** 199 * Gets the view size (is fixed by user). 200 * 201 * @return view size 202 */ 203 public int getViewSize() 204 { 205 return viewSize; 206 } 207 208 /** 209 * view size of the navigation bar. 210 * 211 * @param size 212 */ 213 public void setViewSize(final int size) 214 { 215 viewSize = size; 216 } 217 218 /** 219 * Sets the margin. 220 * 221 * @param margin 222 * the margin 223 */ 224 public void setMargin(final int margin) 225 { 226 this.margin = margin; 227 } 228 229 /** 230 * Sets the seperator. Null meaning, no separator at all. 231 * 232 * @param separator 233 * the seperator 234 */ 235 public void setSeparator(final String separator) 236 { 237 this.separator = separator; 238 } 239 240 @Override 241 protected void onConfigure() 242 { 243 super.onConfigure(); 244 setDefaultModel(new Model<Integer>( 245 (int)Math.max(Integer.MAX_VALUE, pageable.getPageCount()))); 246 // PagingNavigation itself (as well as the PageableListView) 247 // may have pages. 248 249 // The index of the first page link depends on the PageableListView's 250 // page currently printed. 251 setStartIndex(); 252 } 253 254 /** 255 * Allow subclasses replacing populateItem to calculate the current page number 256 * 257 * @return start index 258 */ 259 protected final long getStartIndex() 260 { 261 return startIndex; 262 } 263 264 /** 265 * Populate the current cell with a page link (PagingNavigationLink) enclosing the page number 266 * the link is pointing to. Subclasses may provide there own implementation adding more 267 * sophisticated page links. 268 * 269 * @see org.apache.wicket.markup.html.list.Loop#populateItem(org.apache.wicket.markup.html.list.LoopItem) 270 */ 271 @Override 272 protected void populateItem(final LoopItem loopItem) 273 { 274 // Get the index of page this link shall point to 275 final long pageIndex = getStartIndex() + loopItem.getIndex(); 276 277 // Add a page link pointing to the page 278 final AbstractLink link = newPagingNavigationLink("pageLink", pageable, pageIndex); 279 link.add(new TitleAppender(pageIndex)); 280 loopItem.add(link); 281 282 // Add a page number label to the list which is enclosed by the link 283 String label = ""; 284 if (labelProvider != null) 285 { 286 label = labelProvider.getPageLabel(pageIndex); 287 } 288 else 289 { 290 label = String.valueOf(pageIndex + 1).intern(); 291 } 292 link.add(new Label("pageNumber", label)); 293 } 294 295 /** 296 * Factory method for creating page number links. 297 * 298 * @param id 299 * the component id. 300 * @param pageable 301 * the pageable for the link 302 * @param pageIndex 303 * the page index the link points to 304 * @return the page navigation link. 305 */ 306 protected AbstractLink newPagingNavigationLink(String id, IPageable pageable, long pageIndex) 307 { 308 return new PagingNavigationLink<Void>(id, pageable, pageIndex); 309 } 310 311 /** 312 * Renders the page link. Add the separator if not the last page link 313 * 314 * @see Loop#renderItem(org.apache.wicket.markup.html.list.LoopItem) 315 */ 316 @Override 317 protected void renderItem(final LoopItem loopItem) 318 { 319 // Call default implementation 320 super.renderItem(loopItem); 321 322 // Add separator if not last page 323 if (separator != null && (loopItem.getIndex() != getIterations() - 1)) 324 { 325 getResponse().write(separator); 326 } 327 } 328 329 /** 330 * Get the first page link to render. Adjust the first page link based on the current 331 * PageableListView page displayed. 332 */ 333 private void setStartIndex() 334 { 335 // Which startIndex are we currently using 336 long firstListItem = startIndex; 337 338 // How many page links shall be displayed 339 int viewSize = (int)Math.min(getViewSize(), pageable.getPageCount()); 340 long margin = getMargin(); 341 342 // What is the PageableListView's page index to be displayed 343 long currentPage = pageable.getCurrentPage(); 344 345 // Make sure the current page link index is within the current 346 // window taking the left and right margin into account 347 if (currentPage < (firstListItem + margin)) 348 { 349 firstListItem = currentPage - margin; 350 } 351 else if ((currentPage >= (firstListItem + viewSize - margin))) 352 { 353 354 firstListItem = (currentPage + margin + 1) - viewSize; 355 } 356 357 // Make sure the first index is >= 0 and the last index is <= 358 // than the last page link index. 359 if ((firstListItem + viewSize) >= pageable.getPageCount()) 360 { 361 firstListItem = pageable.getPageCount() - viewSize; 362 } 363 364 if (firstListItem < 0) 365 { 366 firstListItem = 0; 367 } 368 369 if ((viewSize != getIterations()) || (startIndex != firstListItem)) 370 { 371 modelChanging(); 372 373 // Tell the ListView what the new start index shall be 374 addStateChange(); 375 startIndex = firstListItem; 376 377 setIterations((int)Math.min(viewSize, pageable.getPageCount())); 378 379 modelChanged(); 380 381 // force all children to be re-rendered 382 removeAll(); 383 } 384 } 385 386 /** 387 * Set the number of iterations. 388 * 389 * @param i 390 * the number of iterations 391 */ 392 private void setIterations(int i) 393 { 394 setDefaultModelObject(i); 395 } 396 397 /** 398 * Appends title attribute to navigation links 399 * 400 * @author igor.vaynberg 401 */ 402 private final class TitleAppender extends Behavior 403 { 404 private static final long serialVersionUID = 1L; 405 /** resource key for the message */ 406 private static final String RES = "PagingNavigation.page"; 407 /** page number */ 408 private final long page; 409 410 /** 411 * Constructor 412 * 413 * @param page 414 * page number to use as the ${page} var 415 */ 416 public TitleAppender(long page) 417 { 418 this.page = page; 419 } 420 421 /** {@inheritDoc} */ 422 @Override 423 public void onComponentTag(Component component, ComponentTag tag) 424 { 425 String pageIndex = String.valueOf(page + 1).intern(); 426 Map<String, String> vars = Map.of("page", pageIndex); 427 tag.put("title", PagingNavigation.this.getString(RES, Model.ofMap(vars))); 428 } 429 } 430}