001 // Copyright 2007, 2008, 2009, 2010, 2011 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
015 package org.apache.tapestry5.corelib.components;
016
017 import org.apache.tapestry5.*;
018 import org.apache.tapestry5.annotations.Environmental;
019 import org.apache.tapestry5.annotations.Events;
020 import org.apache.tapestry5.annotations.Parameter;
021 import org.apache.tapestry5.dom.Element;
022 import org.apache.tapestry5.grid.GridDataSource;
023 import org.apache.tapestry5.internal.InternalConstants;
024 import org.apache.tapestry5.ioc.Messages;
025 import org.apache.tapestry5.ioc.annotations.Inject;
026 import org.apache.tapestry5.services.ClientBehaviorSupport;
027 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
028
029 /**
030 * Generates a series of links used to jump to a particular page index within the overall data set.
031 *
032 * @tapestrydoc
033 */
034 @Events(InternalConstants.GRID_INPLACE_UPDATE + " (internal event)")
035 public class GridPager
036 {
037 /**
038 * The source of the data displayed by the grid (this is used to determine {@link GridDataSource#getAvailableRows()
039 * how many rows are available}, which in turn determines the page count).
040 */
041 @Parameter(required = true)
042 private GridDataSource source;
043
044 /**
045 * The number of rows displayed per page.
046 */
047 @Parameter(required = true)
048 private int rowsPerPage;
049
050 /**
051 * The current page number (indexed from 1).
052 */
053 @Parameter(required = true)
054 private int currentPage;
055
056 /**
057 * Number of pages before and after the current page in the range. The pager always displays links for 2 * range + 1
058 * pages, unless that's more than the total number of available pages.
059 */
060 @Parameter(BindingConstants.SYMBOL + ":" + ComponentParameterConstants.GRIDPAGER_PAGE_RANGE)
061 private int range;
062
063 /**
064 * If not null, then each link is output as a link to update the specified zone.
065 */
066 @Parameter
067 private String zone;
068
069 private int lastIndex;
070
071 private int maxPages;
072
073 @Inject
074 private ComponentResources resources;
075
076 @Inject
077 private Messages messages;
078
079 @Environmental
080 private ClientBehaviorSupport clientBehaviorSupport;
081
082 @Environmental
083 private JavaScriptSupport jsSupport;
084
085 void beginRender(MarkupWriter writer)
086 {
087 int availableRows = source.getAvailableRows();
088
089 maxPages = ((availableRows - 1) / rowsPerPage) + 1;
090
091 if (maxPages < 2) return;
092
093 writer.element("div", "class", "t-data-grid-pager");
094
095 lastIndex = 0;
096
097 for (int i = 1; i <= 2; i++)
098 writePageLink(writer, i);
099
100 int low = currentPage - range;
101 int high = currentPage + range;
102
103 if (low < 1)
104 {
105 low = 1;
106 high = 2 * range + 1;
107 }
108 else
109 {
110 if (high > maxPages)
111 {
112 high = maxPages;
113 low = high - 2 * range;
114 }
115 }
116
117 for (int i = low; i <= high; i++)
118 writePageLink(writer, i);
119
120 for (int i = maxPages - 1; i <= maxPages; i++)
121 writePageLink(writer, i);
122
123 writer.end();
124 }
125
126 private void writePageLink(MarkupWriter writer, int pageIndex)
127 {
128 if (pageIndex < 1 || pageIndex > maxPages) return;
129
130 if (pageIndex <= lastIndex) return;
131
132 if (pageIndex != lastIndex + 1) writer.write(" ... ");
133
134 lastIndex = pageIndex;
135
136 if (pageIndex == currentPage)
137 {
138 writer.element("span", "class", "current");
139 writer.write(Integer.toString(pageIndex));
140 writer.end();
141 return;
142 }
143
144 Object[] context = zone == null
145 ? new Object[] { pageIndex }
146 : new Object[] { pageIndex, zone };
147
148 Link link = resources.createEventLink(EventConstants.ACTION, context);
149
150 Element element = writer.element("a",
151 "href", zone == null ? link : "#",
152 "title", messages.format("goto-page", pageIndex));
153
154 writer.write(Integer.toString(pageIndex));
155 writer.end();
156
157 if (zone != null)
158 {
159 String id = jsSupport.allocateClientId(resources);
160
161 element.attribute("id", id);
162
163 clientBehaviorSupport.linkZone(id, zone, link);
164 }
165 }
166
167 /**
168 * Normal, non-Ajax event handler.
169 */
170 void onAction(int newPage)
171 {
172 // TODO: Validate newPage in range
173
174 currentPage = newPage;
175 }
176
177 /**
178 * Akjax event handler, passing the zone along.
179 */
180 boolean onAction(int newPage, String zone)
181 {
182 onAction(newPage);
183
184 resources.triggerEvent(InternalConstants.GRID_INPLACE_UPDATE, new Object[] { zone }, null);
185
186 return true; // abort event
187 }
188 }