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