001/*
002 * Copyright 2010 Google Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * 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, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016package com.github.gwtbootstrap.client.ui;
017
018import com.github.gwtbootstrap.client.ui.constants.ButtonType;
019import com.github.gwtbootstrap.client.ui.constants.IconType;
020import com.github.gwtbootstrap.client.ui.constants.Placement;
021import com.google.gwt.core.client.GWT;
022import com.google.gwt.event.dom.client.ClickEvent;
023import com.google.gwt.event.dom.client.ClickHandler;
024import com.google.gwt.i18n.client.Constants;
025import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
026import com.google.gwt.i18n.client.NumberFormat;
027import com.google.gwt.resources.client.ClientBundle;
028import com.google.gwt.uibinder.client.UiConstructor;
029import com.google.gwt.user.cellview.client.AbstractPager;
030import com.google.gwt.user.cellview.client.SimplePager.Style;
031import com.google.gwt.user.client.ui.HTML;
032import com.google.gwt.user.client.ui.HasVerticalAlignment;
033import com.google.gwt.user.client.ui.HorizontalPanel;
034import com.google.gwt.view.client.HasRows;
035import com.google.gwt.view.client.Range;
036
037/**
038 * A pager for controlling a {@link HasRows} that only supports simple page
039 * navigation.
040 *
041 * <p>
042 * <h3>Example</h3>
043 *
044 * <pre>
045 *   <b:CellTable pageSize="10" ui:field="table" width="100%" />
046 *   <div>
047 *     <b:SimplePager display="{table}" location="RIGHT" fastForwardRows="50"/>
048 *   </div>
049 * </pre>
050 *
051 * </p>
052 */
053public class SimplePager extends AbstractPager {
054
055    /**
056     * Constant for labeling the simple pager navigational {@link ImageButton}s
057     */
058    @DefaultLocale("en_US")
059    public interface ImageButtonsConstants extends Constants {
060        @DefaultStringValue("Fast forward")
061        String fastForward();
062
063        @DefaultStringValue("First page")
064        String firstPage();
065
066        @DefaultStringValue("Last page")
067        String lastPage();
068
069        @DefaultStringValue("Next page")
070        String nextPage();
071
072        @DefaultStringValue("Previous page")
073        String prevPage();
074    }
075
076    /**
077     * A ClientBundle that provides styles for this widget.
078     */
079    public static interface Resources extends ClientBundle {
080
081        /**
082         * The styles used in this widget.
083         */
084        @Source("GwtBootstrapSimplePager.css")
085        Style simplePagerStyle();
086    }
087
088    /**
089     * The location of the text relative to the paging buttons.
090     */
091    public static enum TextLocation {
092        CENTER, LEFT, RIGHT;
093    }
094
095    private static final int DEFAULT_FAST_FORWARD_ROWS = 100;
096    private static Resources DEFAULT_RESOURCES;
097
098    private static Resources getDefaultResources() {
099        if (DEFAULT_RESOURCES == null) {
100            DEFAULT_RESOURCES = GWT.create(Resources.class);
101        }
102        return DEFAULT_RESOURCES;
103    }
104
105    private final int tooltipDelay = 1000;
106    private final Placement tooltipPlacement = Placement.BOTTOM;
107
108    private final Button fastForward;
109    private final Tooltip fastForwardTooltip;
110
111    private int fastForwardRows;
112
113    private final Button firstPage;
114    private final Tooltip firstPageTooltip;
115
116    /**
117     * We use an {@link HTML} so we can embed the loading image.
118     */
119    private final HTML label = new HTML();
120
121    private final Button lastPage;
122    private final Button nextPage;
123    private final Button prevPage;
124
125    private final Tooltip lastPageTooltip;
126    private final Tooltip nextPageTooltip;
127    private final Tooltip prevPageTooltip;
128
129    /**
130     * The {@link Resources} used by this widget.
131     */
132    private final Resources resources;
133
134    /**
135     * The {@link Style} used by this widget.
136     */
137    private final Style style;
138
139    /**
140     * Construct a {@link SimplePager} with the default text location.
141     */
142    public SimplePager() {
143        this(TextLocation.CENTER);
144    }
145
146    /**
147     * Construct a {@link SimplePager} with the specified text location.
148     *
149     * @param location
150     *            the location of the text relative to the buttons
151     */
152    @UiConstructor
153    // Hack for Google I/O demo
154    public SimplePager(TextLocation location) {
155        this(location, getDefaultResources(), true, DEFAULT_FAST_FORWARD_ROWS, false);
156    }
157
158    /**
159     * Construct a {@link SimplePager} with the default resources, fast forward
160     * rows and default image button names.
161     *
162     * @param location
163     *            the location of the text relative to the buttons
164     * @param showFastForwardButton
165     *            if true, show a fast-forward button that advances by a larger
166     *            increment than a single page
167     * @param showLastPageButton
168     *            if true, show a button to go the the last page
169     */
170    public SimplePager(TextLocation location, boolean showFastForwardButton, boolean showLastPageButton) {
171        this(location, showFastForwardButton, DEFAULT_FAST_FORWARD_ROWS, showLastPageButton);
172    }
173
174    /**
175     * Construct a {@link SimplePager} with the default resources and default
176     * image button names.
177     *
178     * @param location
179     *            the location of the text relative to the buttons
180     * @param showFastForwardButton
181     *            if true, show a fast-forward button that advances by a larger
182     *            increment than a single page
183     * @param fastForwardRows
184     *            the number of rows to jump when fast forwarding
185     * @param showLastPageButton
186     *            if true, show a button to go the the last page
187     */
188    public SimplePager(TextLocation location, boolean showFastForwardButton, final int fastForwardRows, boolean showLastPageButton) {
189        this(location, getDefaultResources(), showFastForwardButton, fastForwardRows, showLastPageButton);
190    }
191
192    /**
193     * Construct a {@link SimplePager} with the specified resources.
194     *
195     * @param location
196     *            the location of the text relative to the buttons
197     * @param resources
198     *            the {@link Resources} to use
199     * @param showFastForwardButton
200     *            if true, show a fast-forward button that advances by a larger
201     *            increment than a single page
202     * @param fastForwardRows
203     *            the number of rows to jump when fast forwarding
204     * @param showLastPageButton
205     *            if true, show a button to go the the last page
206     * @param imageButtonConstants
207     *            Constants that contain the image button names
208     */
209    public SimplePager(TextLocation location, Resources resources, boolean showFastForwardButton, final int fastForwardRows, boolean showLastPageButton, ImageButtonsConstants imageButtonConstants) {
210        this.resources = resources;
211        this.fastForwardRows = fastForwardRows;
212        this.style = this.resources.simplePagerStyle();
213        this.style.ensureInjected();
214
215        // Create the buttons.
216        firstPage = new Button();
217        firstPage.setType(ButtonType.LINK);
218        firstPage.setIcon(IconType.FAST_BACKWARD);
219        firstPage.addClickHandler(new ClickHandler() {
220            @Override
221            public void onClick(ClickEvent event) {
222                firstPage();
223            }
224        });
225        firstPageTooltip = new Tooltip(imageButtonConstants.firstPage());
226        firstPageTooltip.setWidget(firstPage);
227        firstPageTooltip.setPlacement(tooltipPlacement);
228        firstPageTooltip.setShowDelay(tooltipDelay);
229
230        nextPage = new Button();
231        nextPage.setType(ButtonType.LINK);
232        nextPage.setIcon(IconType.STEP_FORWARD);
233        nextPage.addClickHandler(new ClickHandler() {
234            @Override
235            public void onClick(ClickEvent event) {
236                nextPage();
237            }
238        });
239        nextPageTooltip = new Tooltip(imageButtonConstants.nextPage());
240        nextPageTooltip.setWidget(nextPage);
241        nextPageTooltip.setPlacement(tooltipPlacement);
242        nextPageTooltip.setShowDelay(tooltipDelay);
243
244        prevPage = new Button();
245        prevPage.setType(ButtonType.LINK);
246        prevPage.setIcon(IconType.STEP_BACKWARD);
247        prevPage.addClickHandler(new ClickHandler() {
248            @Override
249            public void onClick(ClickEvent event) {
250                previousPage();
251            }
252        });
253        prevPageTooltip = new Tooltip(imageButtonConstants.prevPage());
254        prevPageTooltip.setWidget(prevPage);
255        prevPageTooltip.setPlacement(tooltipPlacement);
256        prevPageTooltip.setShowDelay(tooltipDelay);
257
258        if (showLastPageButton) {
259            lastPage = new Button();
260            lastPage.setType(ButtonType.LINK);
261            lastPage.setIcon(IconType.FAST_FORWARD);
262            lastPage.addClickHandler(new ClickHandler() {
263                @Override
264                public void onClick(ClickEvent event) {
265                    lastPage();
266                }
267            });
268            lastPageTooltip = new Tooltip(imageButtonConstants.lastPage());
269            lastPageTooltip.setWidget(lastPage);
270            lastPageTooltip.setPlacement(tooltipPlacement);
271            lastPageTooltip.setShowDelay(tooltipDelay);
272        } else {
273            lastPage = null;
274            lastPageTooltip = null;
275        }
276        if (showFastForwardButton) {
277            fastForward = new Button();
278            fastForward.setType(ButtonType.LINK);
279            fastForward.setIcon(IconType.FORWARD);
280            fastForward.addClickHandler(new ClickHandler() {
281                @Override
282                public void onClick(ClickEvent event) {
283                    setPage(getPage() + getFastForwardPages());
284                }
285            });
286            fastForwardTooltip = new Tooltip(imageButtonConstants.fastForward());
287            fastForwardTooltip.setWidget(fastForward);
288            fastForwardTooltip.setPlacement(tooltipPlacement);
289            fastForwardTooltip.setShowDelay(tooltipDelay);
290        } else {
291            fastForward = null;
292            fastForwardTooltip = null;
293        }
294
295        // Construct the widget.
296        HorizontalPanel layout = new HorizontalPanel();
297        layout.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
298        initWidget(layout);
299        if (location == TextLocation.LEFT) {
300            layout.add(label);
301        }
302        layout.add(firstPage);
303        layout.add(prevPage);
304        if (location == TextLocation.CENTER) {
305            layout.add(label);
306        }
307        layout.add(nextPage);
308        if (showFastForwardButton) {
309            layout.add(fastForward);
310        }
311        if (showLastPageButton) {
312            layout.add(lastPage);
313        }
314
315        layout.add(firstPageTooltip);
316        layout.add(prevPageTooltip);
317        layout.add(nextPageTooltip);
318        if (showFastForwardButton) {
319            layout.add(fastForwardTooltip);
320        }
321        if (showLastPageButton) {
322            layout.add(lastPageTooltip);
323        }
324
325        if (location == TextLocation.RIGHT) {
326            layout.add(label);
327        }
328
329        // Add style names to the cells.
330        firstPage.getElement().getParentElement().addClassName(style.button());
331        prevPage.getElement().getParentElement().addClassName(style.button());
332        label.getElement().getParentElement().addClassName(style.pageDetails());
333        nextPage.getElement().getParentElement().addClassName(style.button());
334        if (showFastForwardButton) {
335            fastForward.getElement().getParentElement().addClassName(style.button());
336        }
337        if (showLastPageButton) {
338            lastPage.getElement().getParentElement().addClassName(style.button());
339        }
340
341        // Disable the buttons by default.
342        setDisplay(null);
343    }
344
345    /**
346     * Construct a {@link SimplePager} with the specified resources and default
347     * image button names.
348     *
349     * @param location
350     *            the location of the text relative to the buttons
351     * @param resources
352     *            the {@link Resources} to use
353     * @param showFastForwardButton
354     *            if true, show a fast-forward button that advances by a larger
355     *            increment than a single page
356     * @param fastForwardRows
357     *            the number of rows to jump when fast forwarding
358     * @param showLastPageButton
359     *            if true, show a button to go the the last page
360     */
361    public SimplePager(TextLocation location, Resources resources, boolean showFastForwardButton, final int fastForwardRows, boolean showLastPageButton) {
362        this(location, resources, showFastForwardButton, fastForwardRows, showLastPageButton, GWT.<ImageButtonsConstants> create(ImageButtonsConstants.class));
363    }
364
365    @Override
366    public void firstPage() {
367        super.firstPage();
368    }
369
370    @Override
371    public int getPage() {
372        return super.getPage();
373    }
374
375    @Override
376    public int getPageCount() {
377        return super.getPageCount();
378    }
379
380    @Override
381    public boolean hasNextPage() {
382        return super.hasNextPage();
383    }
384
385    @Override
386    public boolean hasNextPages(int pages) {
387        return super.hasNextPages(pages);
388    }
389
390    @Override
391    public boolean hasPage(int index) {
392        return super.hasPage(index);
393    }
394
395    @Override
396    public boolean hasPreviousPage() {
397        return super.hasPreviousPage();
398    }
399
400    @Override
401    public boolean hasPreviousPages(int pages) {
402        return super.hasPreviousPages(pages);
403    }
404
405    @Override
406    public void lastPage() {
407        super.lastPage();
408    }
409
410    @Override
411    public void lastPageStart() {
412        super.lastPageStart();
413    }
414
415    @Override
416    public void nextPage() {
417        super.nextPage();
418    }
419
420    @Override
421    public void previousPage() {
422        super.previousPage();
423    }
424
425    @Override
426    public void setDisplay(HasRows display) {
427        // Enable or disable all buttons.
428        boolean disableButtons = (display == null);
429        setFastForwardDisabled(disableButtons);
430        setNextPageButtonsDisabled(disableButtons);
431        setPrevPageButtonsDisabled(disableButtons);
432        super.setDisplay(display);
433    }
434
435    @Override
436    public void setPage(int index) {
437        super.setPage(index);
438    }
439
440    @Override
441    public void setPageSize(int pageSize) {
442        super.setPageSize(pageSize);
443    }
444
445    @Override
446    public void setPageStart(int index) {
447        super.setPageStart(index);
448    }
449
450    /**
451     * Let the page know that the table is loading. Call this method to clear
452     * all data from the table and hide the current range when new data is being
453     * loaded into the table.
454     */
455    public void startLoading() {
456        getDisplay().setRowCount(0, true);
457        label.setHTML("");
458    }
459
460    /**
461     * Get the text to display in the pager that reflects the state of the
462     * pager.
463     *
464     * @return the text
465     */
466    protected String createText() {
467        // Default text is 1 based.
468        NumberFormat formatter = NumberFormat.getFormat("#,###");
469        HasRows display = getDisplay();
470        Range range = display.getVisibleRange();
471        int pageStart = range.getStart() + 1;
472        int pageSize = range.getLength();
473        int dataSize = display.getRowCount();
474        int endIndex = Math.min(dataSize, pageStart + pageSize - 1);
475        endIndex = Math.max(pageStart, endIndex);
476        boolean exact = display.isRowCountExact();
477        return formatter.format(pageStart) + "-" + formatter.format(endIndex) + (exact ? " of " : " of over ") + formatter.format(dataSize);
478    }
479
480    @Override
481    protected void onRangeOrRowCountChanged() {
482        HasRows display = getDisplay();
483        label.setText(createText());
484
485        // Update the prev and first buttons.
486        setPrevPageButtonsDisabled(!hasPreviousPage());
487
488        // Update the next and last buttons.
489        if (isRangeLimited() || !display.isRowCountExact()) {
490            setNextPageButtonsDisabled(!hasNextPage());
491            setFastForwardDisabled(!hasNextPages(getFastForwardPages()));
492        }
493    }
494
495    /**
496     * Check if the next button is disabled. Visible for testing.
497     */
498    boolean isNextButtonDisabled() {
499        return !nextPage.isEnabled();
500    }
501
502    /**
503     * Check if the previous button is disabled. Visible for testing.
504     */
505    boolean isPreviousButtonDisabled() {
506        return !prevPage.isEnabled();
507    }
508
509    /**
510     * Get the number of pages to fast forward based on the current page size.
511     *
512     * @return the number of pages to fast forward
513     */
514    private int getFastForwardPages() {
515        int pageSize = getPageSize();
516        return pageSize > 0 ? fastForwardRows / pageSize : 0;
517    }
518
519    public int getFastForwardRows() {
520        return fastForwardRows;
521    }
522
523    public void setFastForwardRows(int fastForwardRows) {
524        this.fastForwardRows = fastForwardRows;
525    }
526
527    /**
528     * Enable or disable the fast forward button.
529     *
530     * @param disabled
531     *            true to disable, false to enable
532     */
533    private void setFastForwardDisabled(boolean disabled) {
534        if (fastForward != null) {
535            fastForward.setEnabled(!disabled);
536            if (disabled) {
537                fastForward.getElement().addClassName(style.disabledButton());
538            } else {
539                fastForward.getElement().removeClassName(style.disabledButton());
540            }
541        }
542    }
543
544    /**
545     * Enable or disable the next page buttons.
546     *
547     * @param disabled
548     *            true to disable, false to enable
549     */
550    private void setNextPageButtonsDisabled(boolean disabled) {
551        nextPage.setEnabled(!disabled);
552        if (disabled) {
553            nextPage.getElement().addClassName(style.disabledButton());
554        } else {
555            nextPage.getElement().removeClassName(style.disabledButton());
556        }
557        if (lastPage != null) {
558            lastPage.setEnabled(!disabled);
559            if (disabled) {
560                lastPage.getElement().addClassName(style.disabledButton());
561            } else {
562                lastPage.getElement().removeClassName(style.disabledButton());
563            }
564        }
565    }
566
567    /**
568     * Enable or disable the previous page buttons.
569     *
570     * @param disabled
571     *            true to disable, false to enable
572     */
573    private void setPrevPageButtonsDisabled(boolean disabled) {
574        firstPage.setEnabled(!disabled);
575        prevPage.setEnabled(!disabled);
576        if (disabled) {
577            firstPage.getElement().addClassName(style.disabledButton());
578            prevPage.getElement().addClassName(style.disabledButton());
579        } else {
580            firstPage.getElement().removeClassName(style.disabledButton());
581            prevPage.getElement().removeClassName(style.disabledButton());
582        }
583    }
584}