001/*
002 *  Copyright 2013 GWT-Bootstrap
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.github.gwtbootstrap.client.ui;
017
018import com.github.gwtbootstrap.client.ui.base.HasId;
019import com.github.gwtbootstrap.client.ui.base.HasStyle;
020import com.github.gwtbootstrap.client.ui.base.IsResponsive;
021import com.github.gwtbootstrap.client.ui.constants.Device;
022import com.github.gwtbootstrap.client.ui.constants.IconPosition;
023import com.github.gwtbootstrap.client.ui.constants.IconSize;
024import com.github.gwtbootstrap.client.ui.constants.IconType;
025import com.google.gwt.event.dom.client.ClickEvent;
026import com.google.gwt.event.dom.client.ClickHandler;
027import com.google.gwt.user.cellview.client.AbstractPager;
028import com.google.gwt.view.client.HasRows;
029import com.google.web.bindery.event.shared.HandlerRegistration;
030
031import java.util.ArrayList;
032import java.util.List;
033
034/**
035 * A pager for controlling a {@link HasRows} using {@link Pagination}.
036 * Just bind the NumberedPager using {@code NumberedPager.setDisplay} to some {@link HasRows} implementation like
037 * {@link CellTable} or {@link DataGrid}, and the pager is automatically generated fully working.
038 * <p/>
039 * <p>
040 * <h3>Example</h3>
041 * <p/>
042 * <pre>
043 *   <code>
044 *     <b:NumberedPager display="{table}"/>
045 *     <b:CellTable pageSize="10" ui:field="table" width="100%" />
046 *   </code>
047 * </pre>
048 * <p/>
049 * </p>
050 */
051public class NumberedPager extends AbstractPager implements HasStyle, IsResponsive, HasId {
052
053    private final Pagination pagination = new Pagination();
054    private final List<HandlerRegistration> handlerRegistrationList = new ArrayList<HandlerRegistration>();
055
056    private int visiblePages = -1;
057    private String nextCustomStyleName;
058    private String previousCustomStyleName;
059    private NavLink nextLink = new NavLink(">");
060    private NavLink previousLink = new NavLink("<");
061
062    public NumberedPager() {
063        initWidget(pagination);
064    }
065
066    /**
067     * {@inheritDoc}
068     */
069    public String getId() {
070        return pagination.getId();
071    }
072
073    /**
074     * {@inheritDoc}
075     */
076    public void setId(String id) {
077        pagination.setId(id);
078    }
079
080    /**
081     * Pulls the widget to the right side.
082     *
083     * @param pullRight <code>true</code> if the widget should be aligned right.
084     */
085    public void setPullRight(boolean pullRight) {
086        pagination.setPullRight(pullRight);
087    }
088
089    public void setAlignment(String alignment) {
090        pagination.setAlignment(alignment);
091    }
092
093    /**
094     * Limit the number of visible pages in the Pagination widget.
095     *
096     * @param visiblePages number of visible pages
097     */
098    public void setVisiblePages(int visiblePages) {
099        this.visiblePages = visiblePages;
100        if (visiblePages > 0) {
101            setRangeLimited(false);
102        }/* else {
103            setRangeLimited(true);
104        }*/
105    }
106
107    /**
108     * {@inheritDoc}
109     */
110    public void setShowOn(Device device) {
111        pagination.setShowOn(device);
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    public void setHideOn(Device device) {
118        pagination.setHideOn(device);
119    }
120
121    public void setSize(Pagination.PaginationSize size) {
122        pagination.setSize(size);
123    }
124
125    /**
126     * {@inheritDoc}
127     */
128    public void setStyle(com.github.gwtbootstrap.client.ui.base.Style style) {
129        pagination.setStyle(style);
130    }
131
132    /**
133     * {@inheritDoc}
134     */
135    public void addStyle(com.github.gwtbootstrap.client.ui.base.Style style) {
136        pagination.addStyle(style);
137    }
138
139    /**
140     * {@inheritDoc}
141     */
142    public void removeStyle(com.github.gwtbootstrap.client.ui.base.Style style) {
143        pagination.removeStyle(style);
144    }
145
146    public void setNextCustomStyleName(String nextCustomStyleName) {
147        this.nextCustomStyleName = nextCustomStyleName;
148    }
149
150    public void setNextText(String nextText) {
151        nextLink.setText(nextText);
152    }
153
154    public void setNextIcon(IconType type) {
155        nextLink.setIcon(type);
156    }
157
158    public void setNextCustomIconStyle(String customIconStyle) {
159        nextLink.setCustomIconStyle(customIconStyle);
160    }
161
162    public void setNextIconPosition(IconPosition position) {
163        nextLink.setIconPosition(position);
164    }
165
166    public void setNextIconSize(IconSize size) {
167        nextLink.setIconSize(size);
168    }
169
170    public void setPreviousCustomStyleName(String previousCustomStyleName) {
171        this.previousCustomStyleName = previousCustomStyleName;
172    }
173
174    public void setPreviousText(String previousText) {
175        previousLink.setText(previousText);
176    }
177
178    public void setPreviousIcon(IconType type) {
179        previousLink.setIcon(type);
180    }
181
182    public void setPreviousCustomIconStyle(String customIconStyle) {
183        previousLink.setCustomIconStyle(customIconStyle);
184    }
185
186    public void setPreviousIconPosition(IconPosition position) {
187        previousLink.setIconPosition(position);
188    }
189
190    public void setPreviousIconSize(IconSize size) {
191        previousLink.setIconSize(size);
192    }
193
194    @Override
195    protected void onRangeOrRowCountChanged() {
196        final HasRows display = super.getDisplay();
197        final int pageSize = super.getPageSize();
198        final int calculatedPage = display.getVisibleRange().getStart() / pageSize;
199        final int pageCount = super.getPageCount();
200
201        // Workaround GWT misbehavior:
202        // Calculating wrong page when dynamically changing pageSize to a lower value and start index is between two pages.
203        // This logic aims to correct the start index.
204        if (pageCount > 0 && (calculatedPage != super.getPage())) {
205            display.setVisibleRange(pageSize * calculatedPage, pageSize);
206        } else {
207            // Proceed with normal paging logic
208            if (pageCount > 0) {
209                if (pagination.getWidgetCount() == 0) {
210                    // Lazy init for not displaying back and forward buttons unnecessarily
211                    initPagination();
212                }
213
214                int widgetCount = pagination.getWidgetCount();
215                if (pageCount + 2 > widgetCount) {
216                    // If there are *more* pages then links, then add the remaining links
217                    for (int i = widgetCount - 1; i <= pageCount; i++) {
218                        NavLink page = pagination.addPageLink(i);
219                        page.addClickHandler(createPageClickHandler(i - 1));
220                    }
221                } else if (pageCount + 2 < widgetCount) {
222                    // If there are *less* pages then links, then remove the exceeding links
223                    for (int i = widgetCount - 2; i > pageCount; i--) {
224                        pagination.remove(i);
225                    }
226                }
227
228                // Set the actual page link as disabled
229                updateButtonsState();
230            } else {
231                if (pagination.getWidgetCount() > 0) {
232                    resetPagination();
233                }
234            }
235        }
236    }
237
238    private void initPagination() {
239        if (previousCustomStyleName != null) previousLink.addStyleName(previousCustomStyleName);
240        handlerRegistrationList.add(previousLink.addClickHandler(new ClickHandler() {
241            @Override
242            public void onClick(ClickEvent event) {
243                NumberedPager.this.previousPage();
244            }
245        }));
246        pagination.add(previousLink);
247
248        NavLink page = pagination.addPageLink(1);
249        page.addClickHandler(new ClickHandler() {
250            @Override
251            public void onClick(ClickEvent event) {
252                NumberedPager.this.setPage(0);
253            }
254        });
255        page.setDisabled(true);
256
257        if (nextCustomStyleName != null) nextLink.addStyleName(nextCustomStyleName);
258        handlerRegistrationList.add(nextLink.addClickHandler(new ClickHandler() {
259            @Override
260            public void onClick(ClickEvent event) {
261                NumberedPager.this.nextPage();
262            }
263        }));
264        pagination.add(nextLink);
265    }
266
267    private void updateButtonsState() {
268        final int pageCount = getPageCount();
269        final int currentPage = getPage();
270
271        // If visiblePages is set, then treat circular exhibition
272        if (visiblePages > 0) {
273            // Calculate offsets
274            final int maxPages = visiblePages < pageCount ? visiblePages : pageCount;
275            final int pagesToShow = Math.min(pageCount, maxPages);
276            int firstVisibleIndex = 1;
277
278            if (currentPage >= pageCount - (maxPages / 2)) {
279                firstVisibleIndex = pageCount - maxPages + 1;
280            } else if (currentPage > maxPages / 2) {
281                firstVisibleIndex = currentPage - (maxPages / 2) + 1;
282            }
283
284            final int firstNotVisibleIndex = pagesToShow + firstVisibleIndex;
285
286            // Set out of range pages as invisible (before initial threshold)
287            for (int i = 1; i < firstVisibleIndex; i++) {
288                pagination.getWidget(i).setVisible(false);
289            } // (after final threshold)
290            for (int i = firstNotVisibleIndex; i < pagination.getWidgetCount() - 1; i++) {
291                pagination.getWidget(i).setVisible(false);
292            }
293
294            // Set in range pages as visible
295            for (int i = firstVisibleIndex; i < firstNotVisibleIndex; i++) {
296                pagination.getWidget(i).setVisible(true);
297            }
298        }
299
300        // Set all numbered buttons as enabled
301        for (int i = 1; i < pagination.getWidgetCount() - 1; i++) {
302            NavLink navLink = (NavLink) pagination.getWidget(i);
303            navLink.setDisabled(false);
304            navLink.setActive(false);
305        }
306
307        // Set the button of the current page as disable
308        NavLink navLink = (NavLink) pagination.getWidget(currentPage + 1);
309        navLink.setDisabled(true);
310        navLink.setActive(true);
311
312        // Update the state of previous and next buttons
313        updatePreviousAndNextButtons();
314    }
315
316    private void updatePreviousAndNextButtons() {
317        previousLink.setDisabled(!super.hasPreviousPage());
318        nextLink.setDisabled(!super.hasNextPage());
319    }
320
321    private ClickHandler createPageClickHandler(final int page) {
322        return new ClickHandler() {
323            @Override
324            public void onClick(ClickEvent event) {
325                NumberedPager.this.setPage(page);
326            }
327        };
328    }
329
330    private void resetPagination() {
331        // Deregister click handlers
332        for (HandlerRegistration handlerRegistration : handlerRegistrationList) {
333            handlerRegistration.removeHandler();
334        }
335        pagination.clear();
336        if (previousCustomStyleName != null) previousLink.removeStyleName(previousCustomStyleName);
337        if (nextCustomStyleName != null) nextLink.removeStyleName(nextCustomStyleName);
338    }
339}