001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.html.navigation.paging;
018
019import java.util.Map;
020
021import org.apache.wicket.Component;
022import org.apache.wicket.behavior.Behavior;
023import org.apache.wicket.markup.ComponentTag;
024import org.apache.wicket.markup.html.basic.Label;
025import org.apache.wicket.markup.html.link.AbstractLink;
026import org.apache.wicket.markup.html.list.Loop;
027import org.apache.wicket.markup.html.list.LoopItem;
028import org.apache.wicket.model.Model;
029
030/**
031 * A navigation for a PageableListView that holds links to other pages of the PageableListView.
032 * <p>
033 * For each row (one page of the list of pages) a {@link PagingNavigationLink}will be added that
034 * contains a {@link Label} with the page number of that link (1..n).
035 *
036 * <pre>
037 *
038 *      &lt;td wicket:id=&quot;navigation&quot;&gt;
039 *              &lt;a wicket:id=&quot;pageLink&quot; href=&quot;SearchCDPage.html&quot;&gt;
040 *                      &lt;span wicket:id=&quot;pageNumber&quot;&gt;1&lt;/span&gt;
041 *              &lt;/a&gt;
042 *      &lt;/td&gt;
043 *
044 * </pre>
045 *
046 * thus renders like:
047 *
048 * <pre>
049 *      1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
050 * </pre>
051 *
052 * </p>
053 * <p>
054 * Override method populateItem to customize the rendering of the navigation. For instance:
055 *
056 * <pre>
057 * protected void populateItem(LoopItem loopItem)
058 * {
059 *      final int page = loopItem.getIteration();
060 *      final PagingNavigationLink link = new PagingNavigationLink(&quot;pageLink&quot;, pageableListView, page);
061 *      if (page &gt; 0)
062 *      {
063 *              loopItem.add(new Label(&quot;separator&quot;, &quot;|&quot;));
064 *      }
065 *      else
066 *      {
067 *              loopItem.add(new Label(&quot;separator&quot;, &quot;&quot;));
068 *      }
069 *      link.add(new Label(&quot;pageNumber&quot;, String.valueOf(page + 1)));
070 *      link.add(new Label(&quot;pageLabel&quot;, &quot;page&quot;));
071 *      loopItem.add(link);
072 * }
073 * </pre>
074 *
075 * With:
076 *
077 * <pre>
078 * &lt;span wicket:id=&quot;navigation&quot;&gt;
079 *      &lt;span wicket:id=&quot;separator&quot;&gt;&lt;/span&gt;
080 *      &lt;a wicket:id=&quot;pageLink&quot; href=&quot;#&quot;&gt;
081 *              &lt;span wicket:id=&quot;pageLabel&quot;&gt;&lt;/span&gt;&lt;span wicket:id=&quot;pageNumber&quot;&gt;&lt;/span&gt;
082 *      &lt;/a&gt;
083 * &lt;/span&gt;
084 * </pre>
085 *
086 * renders like:
087 *
088 * <pre>
089 * page1 | page2 | page3 | page4 | page5 | page6 | page7 | page8 | page9
090 * </pre>
091 *
092 * </p>
093 * Assuming a PageableListView with 1000 entries and not more than 10 lines shall be printed per
094 * page, the navigation bar would have 100 entries. Because this is not feasible PagingNavigation's
095 * navigation bar is pageable as well.
096 * <p>
097 * The page links displayed are automatically adjusted based on the number of page links to be
098 * displayed and a margin. The margin makes sure that the page link pointing to the current page is
099 * not at the left or right end of the page links currently printed and thus providing a better user
100 * experience.
101 * <p>
102 * Use setMargin() and setViewSize() to adjust the navigation's bar view size and margin.
103 * <p>
104 * Please
105 *
106 * @see PagingNavigator for a ready made component which already includes links to the first,
107 *      previous, next and last page.
108 *
109 * @author Jonathan Locke
110 * @author Eelco Hillenius
111 * @author Juergen Donnerstag
112 */
113public class PagingNavigation extends Loop
114{
115        private static final long serialVersionUID = 1L;
116
117        /** The PageableListView this navigation is navigating. */
118        protected IPageable pageable;
119
120        /** The label provider for the text that the links should be displaying. */
121        protected IPagingLabelProvider labelProvider;
122
123        /** Offset for the Loop */
124        private long startIndex;
125
126        /**
127         * Number of links on the left and/or right to keep the current page link somewhere near the
128         * middle.
129         */
130        private long margin = -1;
131
132        /** Default separator between page numbers. Null: no separator. */
133        private String separator = null;
134
135        /**
136         * The maximum number of page links to show.
137         */
138        private int viewSize = 10;
139
140
141        /**
142         * Constructor.
143         *
144         * @param id
145         *            See Component
146         * @param pageable
147         *            The underlying pageable component to navigate
148         */
149        public PagingNavigation(final String id, final IPageable pageable)
150        {
151                this(id, pageable, null);
152        }
153
154        /**
155         * Constructor.
156         *
157         * @param id
158         *            See Component
159         * @param pageable
160         *            The underlying pageable component to navigate
161         * @param labelProvider
162         *            The label provider for the text that the links should be displaying.
163         */
164        public PagingNavigation(final String id, final IPageable pageable,
165                final IPagingLabelProvider labelProvider)
166        {
167                super(id, null);
168                this.pageable = pageable;
169                this.labelProvider = labelProvider;
170                startIndex = 0;
171        }
172
173        /**
174         * Gets the margin, default value is half the view size, unless explicitly set.
175         *
176         * @return the margin
177         */
178        public long getMargin()
179        {
180                if (margin == -1 && viewSize != 0)
181                {
182                        return viewSize / 2;
183                }
184                return margin;
185        }
186
187        /**
188         * Gets the seperator.
189         *
190         * @return the seperator
191         */
192        public String getSeparator()
193        {
194                return separator;
195        }
196
197        /**
198         * Gets the view size (is fixed by user).
199         *
200         * @return view size
201         */
202        public int getViewSize()
203        {
204                return viewSize;
205        }
206
207        /**
208         * view size of the navigation bar.
209         *
210         * @param size
211         */
212        public void setViewSize(final int size)
213        {
214                viewSize = size;
215        }
216
217        /**
218         * Sets the margin.
219         *
220         * @param margin
221         *            the margin
222         */
223        public void setMargin(final int margin)
224        {
225                this.margin = margin;
226        }
227
228        /**
229         * Sets the seperator. Null meaning, no separator at all.
230         *
231         * @param separator
232         *            the seperator
233         */
234        public void setSeparator(final String separator)
235        {
236                this.separator = separator;
237        }
238
239        @Override
240        protected void onConfigure()
241        {
242                super.onConfigure();
243                setDefaultModel(new Model<Integer>(
244                        (int)Math.max(Integer.MAX_VALUE, pageable.getPageCount())));
245                // PagingNavigation itself (as well as the PageableListView)
246                // may have pages.
247
248                // The index of the first page link depends on the PageableListView's
249                // page currently printed.
250                setStartIndex();
251        }
252
253        /**
254         * Allow subclasses replacing populateItem to calculate the current page number
255         *
256         * @return start index
257         */
258        protected final long getStartIndex()
259        {
260                return startIndex;
261        }
262
263        /**
264         * Populate the current cell with a page link (PagingNavigationLink) enclosing the page number
265         * the link is pointing to. Subclasses may provide there own implementation adding more
266         * sophisticated page links.
267         *
268         * @see org.apache.wicket.markup.html.list.Loop#populateItem(org.apache.wicket.markup.html.list.LoopItem)
269         */
270        @Override
271        protected void populateItem(final LoopItem loopItem)
272        {
273                // Get the index of page this link shall point to
274                final long pageIndex = getStartIndex() + loopItem.getIndex();
275
276                // Add a page link pointing to the page
277                final AbstractLink link = newPagingNavigationLink("pageLink", pageable, pageIndex);
278                link.add(new TitleAppender(pageIndex));
279                loopItem.add(link);
280
281                // Add a page number label to the list which is enclosed by the link
282                String label = "";
283                if (labelProvider != null)
284                {
285                        label = labelProvider.getPageLabel(pageIndex);
286                }
287                else
288                {
289                        label = String.valueOf(pageIndex + 1).intern();
290                }
291                link.add(new Label("pageNumber", label));
292        }
293
294        /**
295         * Factory method for creating page number links.
296         *
297         * @param id
298         *            the component id.
299         * @param pageable
300         *            the pageable for the link
301         * @param pageIndex
302         *            the page index the link points to
303         * @return the page navigation link.
304         */
305        protected AbstractLink newPagingNavigationLink(String id, IPageable pageable, long pageIndex)
306        {
307                return new PagingNavigationLink<Void>(id, pageable, pageIndex);
308        }
309
310        /**
311         * Renders the page link. Add the separator if not the last page link
312         *
313         * @see Loop#renderItem(org.apache.wicket.markup.html.list.LoopItem)
314         */
315        @Override
316        protected void renderItem(final LoopItem loopItem)
317        {
318                // Call default implementation
319                super.renderItem(loopItem);
320
321                // Add separator if not last page
322                if (separator != null && (loopItem.getIndex() != getIterations() - 1))
323                {
324                        getResponse().write(separator);
325                }
326        }
327
328        /**
329         * Get the first page link to render. Adjust the first page link based on the current
330         * PageableListView page displayed.
331         */
332        private void setStartIndex()
333        {
334                // Which startIndex are we currently using
335                long firstListItem = startIndex;
336
337                // How many page links shall be displayed
338                int viewSize = (int)Math.min(getViewSize(), pageable.getPageCount());
339                long margin = getMargin();
340
341                // What is the PageableListView's page index to be displayed
342                long currentPage = pageable.getCurrentPage();
343
344                // Make sure the current page link index is within the current
345                // window taking the left and right margin into account
346                if (currentPage < (firstListItem + margin))
347                {
348                        firstListItem = currentPage - margin;
349                }
350                else if ((currentPage >= (firstListItem + viewSize - margin)))
351                {
352
353                        firstListItem = (currentPage + margin + 1) - viewSize;
354                }
355
356                // Make sure the first index is >= 0 and the last index is <=
357                // than the last page link index.
358                if ((firstListItem + viewSize) >= pageable.getPageCount())
359                {
360                        firstListItem = pageable.getPageCount() - viewSize;
361                }
362
363                if (firstListItem < 0)
364                {
365                        firstListItem = 0;
366                }
367
368                if ((viewSize != getIterations()) || (startIndex != firstListItem))
369                {
370                        modelChanging();
371
372                        // Tell the ListView what the new start index shall be
373                        addStateChange();
374                        startIndex = firstListItem;
375
376                        setIterations((int)Math.min(viewSize, pageable.getPageCount()));
377
378                        modelChanged();
379
380                        // force all children to be re-rendered
381                        removeAll();
382                }
383        }
384
385        /**
386         * Set the number of iterations.
387         *
388         * @param i
389         *            the number of iterations
390         */
391        private void setIterations(int i)
392        {
393                setDefaultModelObject(i);
394        }
395
396        /**
397         * Appends title attribute to navigation links
398         *
399         * @author igor.vaynberg
400         */
401        private final class TitleAppender extends Behavior
402        {
403                private static final long serialVersionUID = 1L;
404                /** resource key for the message */
405                private static final String RES = "PagingNavigation.page";
406                /** page number */
407                private final long page;
408
409                /**
410                 * Constructor
411                 *
412                 * @param page
413                 *            page number to use as the ${page} var
414                 */
415                public TitleAppender(long page)
416                {
417                        this.page = page;
418                }
419
420                /** {@inheritDoc} */
421                @Override
422                public void onComponentTag(Component component, ComponentTag tag)
423                {
424                        String pageIndex = String.valueOf(page + 1).intern();
425                        Map<String, String> vars = Map.of("page", pageIndex);
426                        tag.put("title", PagingNavigation.this.getString(RES, Model.ofMap(vars)));
427                }
428        }
429}