001// Copyright 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.corelib.components; 016 017import org.apache.tapestry5.BindingConstants; 018import org.apache.tapestry5.ComponentParameterConstants; 019import org.apache.tapestry5.ComponentResources; 020import org.apache.tapestry5.EventConstants; 021import org.apache.tapestry5.MarkupWriter; 022import org.apache.tapestry5.annotations.Events; 023import org.apache.tapestry5.annotations.Parameter; 024import org.apache.tapestry5.commons.Messages; 025import org.apache.tapestry5.grid.GridDataSource; 026import org.apache.tapestry5.http.Link; 027import org.apache.tapestry5.http.services.Request; 028import org.apache.tapestry5.internal.InternalConstants; 029import org.apache.tapestry5.ioc.annotations.Inject; 030import org.apache.tapestry5.services.compatibility.Compatibility; 031import org.apache.tapestry5.services.compatibility.Trait; 032 033/** 034 * Generates a series of links used to jump to a particular page index within the overall data set. 035 * 036 * @tapestrydoc 037 */ 038@Events(InternalConstants.GRID_INPLACE_UPDATE + " (internal event)") 039public class GridPager 040{ 041 /** 042 * The source of the data displayed by the grid (this is used to determine {@link GridDataSource#getAvailableRows() 043 * how many rows are available}, which in turn determines the page count). 044 */ 045 @Parameter(required = true) 046 private GridDataSource source; 047 048 /** 049 * The number of rows displayed per page. 050 */ 051 @Parameter(required = true) 052 private int rowsPerPage; 053 054 /** 055 * The current page number (indexed from 1). 056 */ 057 @Parameter(required = true) 058 private int currentPage; 059 060 /** 061 * Number of pages before and after the current page in the range. The pager always displays links for 2 * range + 1 062 * pages, unless that's more than the total number of available pages. 063 */ 064 @Parameter(BindingConstants.SYMBOL + ":" + ComponentParameterConstants.GRIDPAGER_PAGE_RANGE) 065 private int range; 066 067 /** 068 * If not null, then each link is output as a link to update the specified zone. 069 */ 070 @Parameter 071 private String zone; 072 073 private int lastIndex; 074 075 private int maxPages; 076 077 @Inject 078 private ComponentResources resources; 079 080 @Inject 081 private Messages messages; 082 083 @Inject 084 private Request request; 085 086 @Inject 087 private Compatibility compatibility; 088 089 private boolean bootstrap4; 090 091 void pageLoaded() 092 { 093 bootstrap4 = compatibility.enabled(Trait.BOOTSTRAP_4); 094 } 095 096 void beginRender(MarkupWriter writer) 097 { 098 int availableRows = source.getAvailableRows(); 099 100 maxPages = ((availableRows - 1) / rowsPerPage) + 1; 101 102 if (maxPages < 2) return; 103 104 writer.element("ul", "class", "pagination"); 105 106 if (zone != null) 107 { 108 writer.attributes("data-inplace-grid-links", true); 109 } 110 111 lastIndex = 0; 112 113 for (int i = 1; i <= 2; i++) 114 writePageLink(writer, i); 115 116 int low = currentPage - range; 117 int high = currentPage + range; 118 119 if (low < 1) 120 { 121 low = 1; 122 high = 2 * range + 1; 123 } else 124 { 125 if (high > maxPages) 126 { 127 high = maxPages; 128 low = high - 2 * range; 129 } 130 } 131 132 for (int i = low; i <= high; i++) 133 writePageLink(writer, i); 134 135 for (int i = maxPages - 1; i <= maxPages; i++) 136 writePageLink(writer, i); 137 138 writer.end(); // ul 139 } 140 141 private void writePageLink(MarkupWriter writer, int pageIndex) 142 { 143 if (pageIndex < 1 || pageIndex > maxPages) return; 144 145 if (pageIndex <= lastIndex) return; 146 147 if (pageIndex != lastIndex + 1) 148 { 149 writer.element("li", "class", bootstrap4 ? "disabled page-item" : "disabled"); 150 writer.element("a", "href", "#", "aria-disabled", "true"); 151 addClassAttributeToPageLinkIfNeeded(writer, bootstrap4); 152 writer.write(" ... "); 153 writer.end(); 154 writer.end(); 155 } 156 157 lastIndex = pageIndex; 158 159 if (pageIndex == currentPage) 160 { 161 writer.element("li", "aria-current", "page", "class", bootstrap4 ? "active page-item" : "active"); 162 writer.element("a", "href", "#", "aria-disabled", "true"); 163 addClassAttributeToPageLinkIfNeeded(writer, bootstrap4); 164 writer.write(Integer.toString(pageIndex)); 165 writer.end(); 166 writer.end(); 167 return; 168 } 169 170 writer.element("li"); 171 if (bootstrap4) 172 { 173 writer.getElement().attribute("class", "page-item"); 174 } 175 176 Link link = resources.createEventLink(EventConstants.ACTION, pageIndex); 177 178 if (zone != null) 179 { 180 link.addParameter("t:inplace", "true"); 181 } 182 183 writer.element("a", 184 "href", link, 185 "data-update-zone", zone, 186 "title", messages.format("core-goto-page", pageIndex)); 187 188 if (bootstrap4) 189 { 190 writer.getElement().attribute("class", "page-link"); 191 } 192 193 writer.write(Integer.toString(pageIndex)); 194 195 writer.end(); 196 197 writer.end(); // li 198 } 199 200 private void addClassAttributeToPageLinkIfNeeded(MarkupWriter writer, final boolean bootstrap4) { 201 if (bootstrap4) 202 { 203 writer.getElement().attribute("class", "page-link"); 204 } 205 } 206 207 /** 208 * Repaging event handler. 209 */ 210 boolean onAction(int newPage) 211 { 212 // TODO: Validate newPage in range 213 214 currentPage = newPage; 215 216 if (request.isXHR()) 217 { 218 resources.triggerEvent(InternalConstants.GRID_INPLACE_UPDATE, null, null); 219 } 220 221 return true; // abort event 222 } 223 224}