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
015package org.apache.tapestry5.corelib.components;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.annotations.Environmental;
019import org.apache.tapestry5.annotations.Events;
020import org.apache.tapestry5.annotations.Parameter;
021import org.apache.tapestry5.dom.Element;
022import org.apache.tapestry5.grid.GridDataSource;
023import org.apache.tapestry5.internal.InternalConstants;
024import org.apache.tapestry5.ioc.Messages;
025import org.apache.tapestry5.ioc.annotations.Inject;
026import org.apache.tapestry5.services.ClientBehaviorSupport;
027import 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)")
035public 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}