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(firstPageTooltip);
304        layout.add(prevPage);
305        layout.add(prevPageTooltip);
306        if (location == TextLocation.CENTER) {
307            layout.add(label);
308        }
309        layout.add(nextPage);
310        layout.add(nextPageTooltip);
311        if (showFastForwardButton) {
312            layout.add(fastForward);
313            layout.add(fastForwardTooltip);
314        }
315        if (showLastPageButton) {
316            layout.add(lastPage);
317            layout.add(lastPageTooltip);
318        }
319
320        if (location == TextLocation.RIGHT) {
321            layout.add(label);
322        }
323
324        // Add style names to the cells.
325        firstPage.getElement().getParentElement().addClassName(style.button());
326        prevPage.getElement().getParentElement().addClassName(style.button());
327        label.getElement().getParentElement().addClassName(style.pageDetails());
328        nextPage.getElement().getParentElement().addClassName(style.button());
329        if (showFastForwardButton) {
330            fastForward.getElement().getParentElement().addClassName(style.button());
331        }
332        if (showLastPageButton) {
333            lastPage.getElement().getParentElement().addClassName(style.button());
334        }
335
336        // Disable the buttons by default.
337        setDisplay(null);
338    }
339
340    /**
341     * Construct a {@link SimplePager} with the specified resources and default
342     * image button names.
343     *
344     * @param location
345     *            the location of the text relative to the buttons
346     * @param resources
347     *            the {@link Resources} to use
348     * @param showFastForwardButton
349     *            if true, show a fast-forward button that advances by a larger
350     *            increment than a single page
351     * @param fastForwardRows
352     *            the number of rows to jump when fast forwarding
353     * @param showLastPageButton
354     *            if true, show a button to go the the last page
355     */
356    public SimplePager(TextLocation location, Resources resources, boolean showFastForwardButton, final int fastForwardRows, boolean showLastPageButton) {
357        this(location, resources, showFastForwardButton, fastForwardRows, showLastPageButton, GWT.<ImageButtonsConstants> create(ImageButtonsConstants.class));
358    }
359
360    @Override
361    public void firstPage() {
362        super.firstPage();
363    }
364
365    @Override
366    public int getPage() {
367        return super.getPage();
368    }
369
370    @Override
371    public int getPageCount() {
372        return super.getPageCount();
373    }
374
375    @Override
376    public boolean hasNextPage() {
377        return super.hasNextPage();
378    }
379
380    @Override
381    public boolean hasNextPages(int pages) {
382        return super.hasNextPages(pages);
383    }
384
385    @Override
386    public boolean hasPage(int index) {
387        return super.hasPage(index);
388    }
389
390    @Override
391    public boolean hasPreviousPage() {
392        return super.hasPreviousPage();
393    }
394
395    @Override
396    public boolean hasPreviousPages(int pages) {
397        return super.hasPreviousPages(pages);
398    }
399
400    @Override
401    public void lastPage() {
402        super.lastPage();
403    }
404
405    @Override
406    public void lastPageStart() {
407        super.lastPageStart();
408    }
409
410    @Override
411    public void nextPage() {
412        super.nextPage();
413    }
414
415    @Override
416    public void previousPage() {
417        super.previousPage();
418    }
419
420    @Override
421    public void setDisplay(HasRows display) {
422        // Enable or disable all buttons.
423        boolean disableButtons = (display == null);
424        setFastForwardDisabled(disableButtons);
425        setNextPageButtonsDisabled(disableButtons);
426        setPrevPageButtonsDisabled(disableButtons);
427        super.setDisplay(display);
428    }
429
430    @Override
431    public void setPage(int index) {
432        super.setPage(index);
433    }
434
435    @Override
436    public void setPageSize(int pageSize) {
437        super.setPageSize(pageSize);
438    }
439
440    @Override
441    public void setPageStart(int index) {
442        super.setPageStart(index);
443    }
444
445    /**
446     * Let the page know that the table is loading. Call this method to clear
447     * all data from the table and hide the current range when new data is being
448     * loaded into the table.
449     */
450    public void startLoading() {
451        getDisplay().setRowCount(0, true);
452        label.setHTML("");
453    }
454
455    /**
456     * Get the text to display in the pager that reflects the state of the
457     * pager.
458     *
459     * @return the text
460     */
461    protected String createText() {
462        // Default text is 1 based.
463        NumberFormat formatter = NumberFormat.getFormat("#,###");
464        HasRows display = getDisplay();
465        Range range = display.getVisibleRange();
466        int pageStart = range.getStart() + 1;
467        int pageSize = range.getLength();
468        int dataSize = display.getRowCount();
469        int endIndex = Math.min(dataSize, pageStart + pageSize - 1);
470        endIndex = Math.max(pageStart, endIndex);
471        boolean exact = display.isRowCountExact();
472        return formatter.format(pageStart) + "-" + formatter.format(endIndex) + (exact ? " of " : " of over ") + formatter.format(dataSize);
473    }
474
475    @Override
476    protected void onRangeOrRowCountChanged() {
477        HasRows display = getDisplay();
478        label.setText(createText());
479
480        // Update the prev and first buttons.
481        setPrevPageButtonsDisabled(!hasPreviousPage());
482
483        // Update the next and last buttons.
484        if (isRangeLimited() || !display.isRowCountExact()) {
485            setNextPageButtonsDisabled(!hasNextPage());
486            setFastForwardDisabled(!hasNextPages(getFastForwardPages()));
487        }
488    }
489
490    /**
491     * Check if the next button is disabled. Visible for testing.
492     */
493    boolean isNextButtonDisabled() {
494        return !nextPage.isEnabled();
495    }
496
497    /**
498     * Check if the previous button is disabled. Visible for testing.
499     */
500    boolean isPreviousButtonDisabled() {
501        return !prevPage.isEnabled();
502    }
503
504    /**
505     * Get the number of pages to fast forward based on the current page size.
506     *
507     * @return the number of pages to fast forward
508     */
509    private int getFastForwardPages() {
510        int pageSize = getPageSize();
511        return pageSize > 0 ? fastForwardRows / pageSize : 0;
512    }
513
514    public int getFastForwardRows() {
515        return fastForwardRows;
516    }
517
518    public void setFastForwardRows(int fastForwardRows) {
519        this.fastForwardRows = fastForwardRows;
520    }
521
522    /**
523     * Enable or disable the fast forward button.
524     *
525     * @param disabled
526     *            true to disable, false to enable
527     */
528    private void setFastForwardDisabled(boolean disabled) {
529        if (fastForward != null) {
530            fastForward.setEnabled(!disabled);
531            if (disabled) {
532                fastForward.getElement().addClassName(style.disabledButton());
533            } else {
534                fastForward.getElement().removeClassName(style.disabledButton());
535            }
536        }
537    }
538
539    /**
540     * Enable or disable the next page buttons.
541     *
542     * @param disabled
543     *            true to disable, false to enable
544     */
545    private void setNextPageButtonsDisabled(boolean disabled) {
546        nextPage.setEnabled(!disabled);
547        if (disabled) {
548            nextPage.getElement().addClassName(style.disabledButton());
549        } else {
550            nextPage.getElement().removeClassName(style.disabledButton());
551        }
552        if (lastPage != null) {
553            lastPage.setEnabled(!disabled);
554            if (disabled) {
555                lastPage.getElement().addClassName(style.disabledButton());
556            } else {
557                lastPage.getElement().removeClassName(style.disabledButton());
558            }
559        }
560    }
561
562    /**
563     * Enable or disable the previous page buttons.
564     *
565     * @param disabled
566     *            true to disable, false to enable
567     */
568    private void setPrevPageButtonsDisabled(boolean disabled) {
569        firstPage.setEnabled(!disabled);
570        prevPage.setEnabled(!disabled);
571        if (disabled) {
572            firstPage.getElement().addClassName(style.disabledButton());
573            prevPage.getElement().addClassName(style.disabledButton());
574        } else {
575            firstPage.getElement().removeClassName(style.disabledButton());
576            prevPage.getElement().removeClassName(style.disabledButton());
577        }
578    }
579}