001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2017 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.client.ui;
021
022import com.google.gwt.core.client.Scheduler;
023import com.google.gwt.dom.client.Document;
024import com.google.gwt.event.dom.client.ClickEvent;
025import com.google.gwt.event.shared.HandlerRegistration;
026import com.google.gwt.user.client.Event;
027import gwt.material.design.client.base.MaterialWidget;
028import gwt.material.design.client.constants.CssName;
029import gwt.material.design.client.constants.IconPosition;
030import gwt.material.design.client.constants.IconType;
031import gwt.material.design.client.constants.WavesType;
032import gwt.material.design.client.events.PageSelectionEvent;
033import gwt.material.design.client.ui.html.ListItem;
034
035import static gwt.material.design.client.events.PageSelectionEvent.PageSelectionHandler;
036import static gwt.material.design.client.events.PageSelectionEvent.TYPE;
037
038//@formatter:off
039
040/**
041 * Material Pager with page event
042 * <h3>UiBinder Usage:</h3>
043 * <pre>
044 * {@code <m:MaterialPager  ui:field='pager' />}
045 * </pre>
046 *
047 * @author Guaido79
048 */
049public class MaterialPager extends MaterialWidget {
050
051    private int total;
052    private int pageSize = 10;
053    private int currentPage = 1;
054    private int maxPageLinksShown = 10;
055    private int calcTotalPages;
056    private int calcShowingPageFrom;
057    private int calcShowingPageTo;
058    private boolean enableIndicator;
059    private boolean calcInitialized;
060    private String indicatorTemplate = "Page {page} of {total}";
061    private PagerListItem linkLeft;
062    private PagerListItem linkRight;
063    private MaterialChip indicator;
064
065    public MaterialPager() {
066        super(Document.get().createULElement(), CssName.PAGINATION);
067        setWaves(WavesType.DEFAULT);
068        removeStyleName(CssName.WAVES_EFFECT);
069    }
070
071    public MaterialPager(int total, int pageSize) {
072        this();
073        this.total = total;
074        this.pageSize = pageSize;
075    }
076
077    public MaterialPager(int total, int pageSize, int currentPage) {
078        this(total, pageSize);
079        this.currentPage = currentPage;
080    }
081
082    @Override
083    protected void onLoad() {
084        super.onLoad();
085
086        load();
087    }
088
089    protected void load() {
090        if (!calcInitialized) {
091            calcTotalPages = total / pageSize + (((double) total % (double) pageSize) > 0 ? 1 : 0);
092
093            add(getOrCreateLiElementLeft());
094            moveNextPagesRange();
095            add(getOrCreateLiElementRight());
096            if (enableIndicator) {
097                add(createLiElementIndicator());
098            }
099            onPageSelection(1);
100
101            calcInitialized = true;
102        }
103    }
104
105    protected void moveNextPagesRange() {
106        calcShowingPageFrom = currentPage;
107        calcShowingPageTo = Math.min(currentPage + maxPageLinksShown - 1, calcTotalPages);
108        createPageNumberLinks();
109    }
110
111    protected void movePreviousPagesRange() {
112        calcShowingPageFrom = currentPage - maxPageLinksShown + 1;
113        calcShowingPageTo = currentPage;
114        createPageNumberLinks();
115    }
116
117    protected void createPageNumberLinks() {
118        for (int i = 0; i < getWidgetCount(); i++) {
119            final PagerListItem widget = (PagerListItem) getWidget(i);
120            if (!widget.isFixed()) {
121                Scheduler.get().scheduleDeferred(widget::removeFromParent);
122            }
123        }
124        int insertionIndex = 1;
125        for (int i = calcShowingPageFrom; i <= calcShowingPageTo; i++) {
126            final PagerListItem liElementForPage = createLiElementForPage(i);
127
128            Scheduler.get().scheduleDeferred(new InsertElementAtPositionCommand(insertionIndex++) {
129                @Override
130                public void execute() {
131                    insert(liElementForPage, insertionIndex);
132                }
133            });
134        }
135    }
136
137    protected PagerListItem createLiElementForPage(final int page) {
138        final PagerListItem pageLiElement = new PagerListItem();
139        pageLiElement.setFixed(false);
140        pageLiElement.add(createLinkPage(page));
141
142        registerHandler(addPageSelectionHandler(event -> pageLiElement.setActive(event.getPageTo() == page)));
143        registerHandler(pageLiElement.addHandler(event -> {
144            onPageSelection(page);
145            event.preventDefault();
146            event.stopPropagation();
147        }, ClickEvent.getType()));
148
149        return pageLiElement;
150    }
151
152    protected PagerListItem getOrCreateLiElementLeft() {
153        linkLeft = new PagerListItem();
154        linkLeft.setFixed(true);
155        registerHandler(linkLeft.addHandler(event -> {
156            if (linkLeft.isEnabled()) {
157                onPageSelection(currentPage - 1);
158            }
159            event.preventDefault();
160            event.stopPropagation();
161        }, ClickEvent.getType()));
162        linkLeft.add(createLinkLeft());
163        registerHandler(addPageSelectionHandler(event -> MaterialPager.this.linkLeft.setEnabled(event.getPageTo() > 1)));
164        return linkLeft;
165    }
166
167    protected PagerListItem getOrCreateLiElementRight() {
168        linkRight = new PagerListItem();
169        linkRight.setFixed(true);
170        registerHandler(linkRight.addHandler(event -> {
171            if (linkRight.isEnabled()) {
172                onPageSelection(currentPage + 1);
173            }
174            event.stopPropagation();
175            event.preventDefault();
176        }, ClickEvent.getType()));
177        linkRight.add(createLinkRight());
178        registerHandler(addPageSelectionHandler(event -> MaterialPager.this.linkRight.setEnabled(event.getPageTo() < calcTotalPages)));
179        return linkRight;
180    }
181
182    protected PagerListItem createLiElementIndicator() {
183        PagerListItem indicatorLi = new PagerListItem(false);
184        indicatorLi.setFixed(true);
185        indicatorLi.add(getOrCreateIndicator());
186        return indicatorLi;
187    }
188
189    protected MaterialChip getOrCreateIndicator() {
190        indicator = new MaterialChip();
191        indicator.getElement().getStyle().setBackgroundColor("inherit");
192        registerHandler(addPageSelectionHandler(event -> indicator.setText(indicatorTemplate
193                .replaceAll("\\{page\\}", String.valueOf(event.getPageTo()))
194                .replaceAll("\\{total\\}", String.valueOf(event.getTotalPage()))
195        )));
196        return indicator;
197    }
198
199    protected MaterialLink createLinkPage(int page) {
200        return new MaterialLink(String.valueOf(page));
201    }
202
203    protected MaterialLink createLinkLeft() {
204        final MaterialLink linkLeft = new MaterialLink(IconType.CHEVRON_LEFT);
205        linkLeft.setIconPosition(IconPosition.NONE);
206        return linkLeft;
207    }
208
209    protected MaterialLink createLinkRight() {
210        final MaterialLink linkRight = new MaterialLink(IconType.CHEVRON_RIGHT);
211        linkRight.setIconPosition(IconPosition.NONE);
212        return linkRight;
213    }
214
215    protected void onPageSelection(int page) {
216        currentPage = page;
217
218        if (currentPage > calcShowingPageTo) {
219            moveNextPagesRange();
220        }
221        if (currentPage < calcShowingPageFrom) {
222            movePreviousPagesRange();
223        }
224
225        PageSelectionEvent event = new PageSelectionEvent();
226        event.setPageFrom(currentPage);
227        event.setPageTo(page);
228        event.setTotalPage(calcTotalPages);
229
230        fireEvent(event);
231    }
232
233    public HandlerRegistration addPageSelectionHandler(PageSelectionHandler handler) {
234        return addHandler(handler, TYPE);
235    }
236
237    public boolean isEnableIndicator() {
238        return enableIndicator;
239    }
240
241    public void setEnableIndicator(boolean enableIndicator) {
242        this.enableIndicator = enableIndicator;
243    }
244
245    public int getTotal() {
246        return total;
247    }
248
249    public void setTotal(final int total) {
250        boolean needToClear = total != this.total;
251        this.total = total;
252        currentPage = 1;
253        if (calcInitialized && needToClear) {
254            clear();
255            load();
256        }
257    }
258
259    public int getPageSize() {
260        return pageSize;
261    }
262
263    public void setPageSize(int pageSize) {
264        this.pageSize = pageSize;
265    }
266
267    public int getCurrentPage() {
268        return currentPage;
269    }
270
271    public void setCurrentPage(int currentPage) {
272        this.currentPage = currentPage;
273    }
274
275    public int getMaxPageLinksShown() {
276        return maxPageLinksShown;
277    }
278
279    public void setMaxPageLinksShown(int maxPageLinksShown) {
280        this.maxPageLinksShown = maxPageLinksShown;
281    }
282
283    public String getIndicatorTemplate() {
284        return indicatorTemplate;
285    }
286
287    /**
288     * Set the paging indicator label with a custom template
289     * <ul>
290     * <li><strong>{page}</strong> is the current page</li>
291     * <li><strong>{total}</strong> is the total page</li>
292     * </ul>
293     * Example
294     * <pre>
295     * {@code
296     * Page {page} of {total}
297     * }</pre>
298     */
299    public void setIndicatorTemplate(String indicatorTemplate) {
300        this.indicatorTemplate = indicatorTemplate;
301    }
302
303    static abstract class InsertElementAtPositionCommand implements Scheduler.ScheduledCommand {
304        protected int insertionIndex;
305
306        InsertElementAtPositionCommand(int insertionIndex) {
307            this.insertionIndex = insertionIndex;
308        }
309    }
310
311    static class PagerListItem extends ListItem {
312        private boolean fixed;
313        private boolean enabled;
314
315        public PagerListItem() {
316            this(true);
317        }
318
319        public PagerListItem(boolean clickable) {
320            if (clickable) {
321                addStyleName(CssName.WAVES_EFFECT);
322                sinkEvents(Event.ONCLICK | Event.TOUCHEVENTS);
323            }
324        }
325
326        public boolean isFixed() {
327            return fixed;
328        }
329
330        public void setFixed(boolean fixed) {
331            this.fixed = fixed;
332        }
333
334        public void setActive(boolean active) {
335            if (active) {
336                addStyleName(CssName.ACTIVE);
337            } else {
338                removeStyleName(CssName.ACTIVE);
339            }
340        }
341
342        @Override
343        public boolean isEnabled() {
344            return enabled;
345        }
346
347        @Override
348        public void setEnabled(boolean enabled) {
349            this.enabled = enabled;
350            if (!enabled) {
351                addStyleName(CssName.DISABLED);
352            } else {
353                removeStyleName(CssName.DISABLED);
354            }
355        }
356    }
357
358    public MaterialChip getIndicator() {
359        return indicator;
360    }
361}