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    }