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 void beginRender(MarkupWriter writer) 090 { 091 int availableRows = source.getAvailableRows(); 092 093 maxPages = ((availableRows - 1) / rowsPerPage) + 1; 094 095 if (maxPages < 2) return; 096 097 writer.element("ul", "class", "pagination"); 098 099 if (zone != null) 100 { 101 writer.attributes("data-inplace-grid-links", true); 102 } 103 104 lastIndex = 0; 105 106 for (int i = 1; i <= 2; i++) 107 writePageLink(writer, i); 108 109 int low = currentPage - range; 110 int high = currentPage + range; 111 112 if (low < 1) 113 { 114 low = 1; 115 high = 2 * range + 1; 116 } else 117 { 118 if (high > maxPages) 119 { 120 high = maxPages; 121 low = high - 2 * range; 122 } 123 } 124 125 for (int i = low; i <= high; i++) 126 writePageLink(writer, i); 127 128 for (int i = maxPages - 1; i <= maxPages; i++) 129 writePageLink(writer, i); 130 131 writer.end(); // ul 132 } 133 134 private void writePageLink(MarkupWriter writer, int pageIndex) 135 { 136 if (pageIndex < 1 || pageIndex > maxPages) return; 137 138 if (pageIndex <= lastIndex) return; 139 140 final boolean isBootstrap4 = isBootstrap4(); 141 142 if (pageIndex != lastIndex + 1) 143 { 144 writer.element("li", "class", isBootstrap4 ? "disabled page-item" : "disabled"); 145 writer.element("a", "href", "#", "aria-disabled", "true"); 146 addClassAttributeToPageLinkIfNeeded(writer, isBootstrap4); 147 writer.write(" ... "); 148 writer.end(); 149 writer.end(); 150 } 151 152 lastIndex = pageIndex; 153 154 if (pageIndex == currentPage) 155 { 156 writer.element("li", "aria-current", "page", "class", isBootstrap4 ? "active page-item" : "active"); 157 writer.element("a", "href", "#", "aria-disabled", "true"); 158 addClassAttributeToPageLinkIfNeeded(writer, isBootstrap4); 159 writer.write(Integer.toString(pageIndex)); 160 writer.end(); 161 writer.end(); 162 return; 163 } 164 165 writer.element("li"); 166 if (isBootstrap4) 167 { 168 writer.getElement().attribute("class", "page-item"); 169 } 170 171 Link link = resources.createEventLink(EventConstants.ACTION, pageIndex); 172 173 if (zone != null) 174 { 175 link.addParameter("t:inplace", "true"); 176 } 177 178 writer.element("a", 179 "href", link, 180 "data-update-zone", zone, 181 "title", messages.format("core-goto-page", pageIndex)); 182 183 addClassAttributeToPageLinkIfNeeded(writer, isBootstrap4); 184 185 writer.write(Integer.toString(pageIndex)); 186 187 writer.end(); 188 189 writer.end(); // li 190 } 191 192 private void addClassAttributeToPageLinkIfNeeded(MarkupWriter writer, final boolean isBootstrap4) { 193 if (isBootstrap4) 194 { 195 writer.getElement().attribute("class", "page-link"); 196 } 197 } 198 199 /** 200 * Repaging event handler. 201 */ 202 boolean onAction(int newPage) 203 { 204 // TODO: Validate newPage in range 205 206 currentPage = newPage; 207 208 if (request.isXHR()) 209 { 210 resources.triggerEvent(InternalConstants.GRID_INPLACE_UPDATE, null, null); 211 } 212 213 return true; // abort event 214 } 215 216 protected boolean isBootstrap4() 217 { 218 return compatibility.enabled(Trait.BOOTSTRAP_4); 219 } 220}