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.Element;
024import com.google.gwt.event.logical.shared.HasSelectionHandlers;
025import com.google.gwt.event.logical.shared.SelectionEvent;
026import com.google.gwt.event.logical.shared.SelectionHandler;
027import com.google.gwt.event.shared.HandlerRegistration;
028import com.google.gwt.user.client.ui.Widget;
029import gwt.material.design.client.base.HasType;
030import gwt.material.design.client.base.JsLoader;
031import gwt.material.design.client.base.MaterialWidget;
032import gwt.material.design.client.base.mixin.ColorsMixin;
033import gwt.material.design.client.base.mixin.CssTypeMixin;
034import gwt.material.design.client.constants.Color;
035import gwt.material.design.client.constants.CssName;
036import gwt.material.design.client.constants.TabType;
037import gwt.material.design.client.ui.html.UnorderedList;
038
039import static gwt.material.design.client.js.JsMaterialElement.$;
040
041//@formatter:off
042
043/**
044 * The tabs structure consists of an unordered list of tabs that have hashes corresponding to tab ids.
045 * Then when you click on each tab, only the container with the corresponding tab id will become visible.
046 * <p>
047 * <h3>UiBinder Usage:</h3>
048 * <pre>
049 * {@code
050 * <m:MaterialTab ui:field="tab"  backgroundColor="BLUE">
051 *     <m:MaterialTabItem waves="YELLOW" grid="l4"><i:Link text="Tab 1" href="#tab1" textColor="WHITE"/></m:MaterialTabItem>
052 *     <m:MaterialTabItem waves="YELLOW" grid="l4"><i:Link text="Tab 2" href="#tab2" textColor="WHITE"/></m:MaterialTabItem>
053 *     <m:MaterialTabItem waves="YELLOW" grid="l4"><i:Link text="Tab 3" href="#tab3" textColor="WHITE"/></m:MaterialTabItem>
054 * </m:MaterialTab>
055 *
056 * <m:MaterialPanel m:id="tab1">
057 *     <m:MaterialTitle title="Tab 1" description="Tab 1 Content"/>
058 * </m:MaterialPanel>
059 * <m:MaterialPanel m:id="tab2">
060 *     <m:MaterialTitle title="Tab 2" description="Tab 2 Content"/>
061 * <m:MaterialPanel>
062 * <i:Panel m:id="tab3">
063 *     <m:MaterialTitle title="Tab 3" description="Tab 3 Content"/>
064 * </m:MaterialPanel>
065 * }
066 * </pre>
067 *
068 * @author kevzlou7979
069 * @author Ben Dol
070 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#tabs">Material Tabs</a>
071 * @see <a href="https://material.io/guidelines/components/tabs.html">Material Design Specification</a>
072 */
073//@formatter:on
074public class MaterialTab extends UnorderedList implements JsLoader, HasType<TabType>, HasSelectionHandlers<Integer> {
075
076    private Color indicatorColor;
077    private MaterialWidget indicator;
078
079    private ColorsMixin<MaterialWidget> indicatorColorMixin;
080    private CssTypeMixin<TabType, MaterialTab> typeMixin;
081
082    public MaterialTab() {
083        super(CssName.TABS);
084    }
085
086    @Override
087    protected void onLoad() {
088        super.onLoad();
089
090        load();
091    }
092
093    @Override
094    public void load() {
095        if (getWidgetCount() > 0) {
096            $(getElement()).tabs();
097            applyIndicator();
098            getChildren().forEach(child -> registerChildHandler(child));
099        }
100    }
101
102    @Override
103    protected void onUnload() {
104        super.onUnload();
105
106        unload();
107    }
108
109    @Override
110    public void unload() {
111        clearAllIndicators();
112    }
113
114    @Override
115    public void reload() {
116        if (isAttached()) {
117            unload();
118            load();
119        }
120    }
121
122    /**
123     * Select a given tab by id.
124     *
125     * @param tabId Tab to selects id.
126     */
127    public void selectTab(String tabId) {
128        $(getElement()).tabs("select_tab", tabId);
129    }
130
131    @Override
132    public void add(Widget child) {
133        super.add(child);
134        reload();
135    }
136
137    protected void registerChildHandler(Widget child) {
138        if (child instanceof MaterialTabItem) {
139            MaterialTabItem item = (MaterialTabItem) child;
140            item.getHandlerRegistry().clearHandlers();
141            item.registerHandler(item.addMouseDownHandler(e -> SelectionEvent.fire(MaterialTab.this, getChildren().indexOf(child))));
142        }
143    }
144
145    @Override
146    public void clear() {
147        super.clear();
148        reload();
149    }
150
151    @Override
152    public boolean remove(Widget w) {
153        boolean value = super.remove(w);
154        reload();
155        return value;
156    }
157
158    @Override
159    public void insert(Widget child, int beforeIndex) {
160        super.insert(child, beforeIndex);
161        reload();
162    }
163
164    protected void applyIndicator() {
165        indicator = new MaterialWidget(getIndicatorElement());
166        indicatorColorMixin = new ColorsMixin<>(indicator);
167        setIndicatorColor(indicatorColor);
168        clearAllIndicators();
169    }
170
171    protected void clearAllIndicators() {
172        Scheduler.get().scheduleDeferred(() -> {
173            for (int i = 1; i < $(getElement()).find(".indicator").length(); i++) {
174                $(getElement()).find(".indicator").eq(i).remove();
175            }
176        });
177    }
178
179    public int getTabIndex() {
180        return $(getElement()).find("li:has(a.active)").index();
181    }
182
183    public void setTabIndex(int tabIndex) {
184        int i = 0;
185        for (Widget w : this) {
186            if (i == tabIndex) {
187                if (w instanceof MaterialTabItem) {
188                    ((MaterialTabItem) w).selectTab();
189                    break;
190                }
191            }
192            i++;
193        }
194    }
195
196    public void setIndicatorColor(Color indicatorColor) {
197        this.indicatorColor = indicatorColor;
198
199        if (indicatorColorMixin != null && indicatorColor != null) {
200            indicatorColorMixin.setBackgroundColor(indicatorColor);
201        }
202    }
203
204    protected Element getIndicatorElement() {
205        return $(getElement()).find(".indicator").last().asElement();
206    }
207
208    @Override
209    public void setType(TabType type) {
210        getTypeMixin().setType(type);
211    }
212
213    @Override
214    public TabType getType() {
215        return getTypeMixin().getType();
216    }
217
218    @Override
219    public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler) {
220        return addHandler(handler, SelectionEvent.getType());
221    }
222
223    protected CssTypeMixin<TabType, MaterialTab> getTypeMixin() {
224        if (typeMixin == null) {
225            typeMixin = new CssTypeMixin<>(this);
226        }
227        return typeMixin;
228    }
229}