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.GWT;
023import com.google.gwt.dom.client.Document;
024import com.google.gwt.dom.client.Element;
025import com.google.gwt.event.shared.HandlerRegistration;
026import com.google.gwt.user.client.ui.Widget;
027import gwt.material.design.client.base.*;
028import gwt.material.design.client.base.mixin.CssTypeMixin;
029import gwt.material.design.client.constants.CollapsibleType;
030import gwt.material.design.client.constants.CssName;
031import gwt.material.design.client.events.ClearActiveEvent;
032import gwt.material.design.client.events.ClearActiveEvent.ClearActiveHandler;
033
034import static gwt.material.design.client.js.JsMaterialElement.$;
035
036//@formatter:off
037
038/**
039 * Collapsible are accordion elements that expand when clicked on.
040 * They allow you to hide content that is not immediately relevant
041 * to the user.
042 * <p>
043 * <h3>UiBinder Usage:</h3>
044 * <p>
045 * <pre>
046 * {@code
047 * // Accordion
048 * <m:MaterialCollapsible accordion="true" grid="s12 m6 l8">
049 *   <!-- ITEM 1 -->
050 *   <m:MaterialCollapsibleItem>
051 *     <m:MaterialCollapsibleHeader>
052 *       <m:MaterialLink text="First" iconType="POLYMER" iconPosition="LEFT" textColor="BLACK"/>
053 *     </m:MaterialCollapsibleHeader>
054 *     <m:MaterialCollapsibleBody>
055 *       <m:MaterialLabel text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."/>
056 *     </m:MaterialCollapsibleBody>
057 *   </m:MaterialCollapsibleItem>
058 * </m:MaterialCollapsible>
059 *
060 * // Expandable
061 * <m:MaterialCollapsible accordion="false" grid="s12 m6 l8">
062 *   <!-- ITEM 1 -->
063 *   <m:MaterialCollapsibleItem>
064 *     <m:MaterialCollapsibleHeader>
065 *       <m:MaterialLink text="First" iconType="POLYMER" iconPosition="LEFT" textColor="BLACK"/>
066 *     </m:MaterialCollapsibleHeader>
067 *     <m:MaterialCollapsibleBody>
068 *       <m:MaterialLabel text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."/>
069 *     </m:MaterialCollapsibleBody>
070 *   </m:MaterialCollapsibleItem>
071 * </m:MaterialCollapsible>
072 *
073 * // Popout
074 * <m:MaterialCollapsible type="POPOUT" grid="s12 m6 l8">
075 *   <!-- ITEM 1 -->
076 *     <m:MaterialCollapsibleItem>
077 *     <m:MaterialCollapsibleHeader>
078 *       <m:MaterialLink text="First" iconType="POLYMER" iconPosition="LEFT" textColor="BLACK"/>
079 *     </m:MaterialCollapsibleHeader>
080 *     <m:MaterialCollapsibleBody>
081 *       <m:MaterialLabel text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."/>
082 *     </m:MaterialCollapsibleBody>
083 *   </m:MaterialCollapsibleItem>
084 * </m:MaterialCollapsible>
085 * }
086 * </pre>
087 *
088 * @author kevzlou7979
089 * @author Ben Dol
090 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#collapsible">Material Collapsibles</a>
091 * @see <a href="https://material.io/guidelines/components/expansion-panels.html#expansion-panels-behavior">Material Design Specification</a>
092 */
093//@formatter:on
094public class MaterialCollapsible extends MaterialWidget implements JsLoader, HasType<CollapsibleType>, HasActiveParent, HasNoSideNavSelection, HasClearActiveHandler {
095
096    protected interface HasCollapsibleParent {
097        void setParent(MaterialCollapsible parent);
098    }
099
100    private boolean accordion = true;
101    private int activeIndex = -1;
102    private Widget activeWidget;
103
104    private CssTypeMixin<CollapsibleType, MaterialCollapsible> typeMixin;
105
106    public MaterialCollapsible() {
107        super(Document.get().createULElement(), CssName.COLLAPSIBLE);
108
109        // Items need to be added after the widget has loaded to avoid
110        // premature configuration issues.
111        enableFeature(Feature.ONLOAD_ADD_QUEUE, true);
112    }
113
114    public MaterialCollapsible(final MaterialCollapsibleItem... widgets) {
115        this();
116
117        for (final MaterialCollapsibleItem item : widgets) {
118            add(item);
119        }
120    }
121
122    @Override
123    protected void onLoad() {
124        super.onLoad();
125
126        if (activeIndex != -1 && activeWidget == null) {
127            setActive(activeIndex);
128        }
129
130        load();
131    }
132
133    @Override
134    public void load() {
135        collapsible(getElement());
136    }
137
138    @Override
139    public void unload() {
140    }
141
142    @Override
143    public void reload() {
144        unload();
145        load();
146    }
147
148    @Override
149    public void add(final Widget child) {
150        if (child instanceof MaterialCollapsibleItem) {
151            ((MaterialCollapsibleItem) child).setParent(this);
152        }
153        super.add(child);
154    }
155
156    @Override
157    public boolean remove(Widget w) {
158        if (w instanceof MaterialCollapsibleItem) {
159            ((MaterialCollapsibleItem) w).setParent(null);
160        }
161        w.removeStyleName(CssName.ACTIVE);
162
163        return super.remove(w);
164    }
165
166    @Override
167    public void clearActive() {
168        clearActiveClass(this);
169        ClearActiveEvent.fire(this);
170    }
171
172    /**
173     * Open the given collapsible item.
174     *
175     * @param index the one-based collapsible item index.
176     */
177    public void open(int index) {
178        setActive(index, true);
179    }
180
181    /**
182     * Close the given collapsible item.
183     *
184     * @param index the one-based collapsible item index.
185     */
186    public void close(int index) {
187        setActive(index, false);
188    }
189
190    /**
191     * Close all the collapsible items.
192     */
193    public void closeAll() {
194        clearActive();
195        reload();
196    }
197
198    @Override
199    public void setEnabled(boolean enabled) {
200        getEnabledMixin().setEnabled(this, enabled);
201    }
202
203    @Override
204    public void setType(CollapsibleType type) {
205        getTypeMixin().setType(type);
206    }
207
208    @Override
209    public CollapsibleType getType() {
210        return getTypeMixin().getType();
211    }
212
213    protected void collapsible(final Element e) {
214        $(e).collapsible(isAccordion());
215    }
216
217    /**
218     * Configure if you want this collapsible container to
219     * accordion its child elements or use expandable.
220     */
221    public void setAccordion(boolean accordion) {
222        getElement().setAttribute("data-collapsible", accordion ? CssName.ACCORDION : CssName.EXPANDABLE);
223        reload();
224    }
225
226    /**
227     * Is the collapsible an 'accordion' type.
228     */
229    public boolean isAccordion() {
230        return getElement().getAttribute("data-collapsible").equals(CssName.ACCORDION);
231    }
232
233    @Override
234    public void setActive(int index) {
235        clearActive();
236        setActive(index, true);
237    }
238
239    @Override
240    public void setActive(int index, boolean active) {
241        activeIndex = index;
242        if (isAttached()) {
243            if (index <= getWidgetCount()) {
244                if (index != 0) {
245                    activeWidget = getWidget(index - 1);
246                    if (activeWidget != null && activeWidget instanceof MaterialCollapsibleItem) {
247                        ((MaterialCollapsibleItem) activeWidget).setActive(active);
248                        reload();
249                    }
250                } else {
251                    GWT.log("The active index must be a one-base index to mark as active.", new IndexOutOfBoundsException());
252                }
253            }
254        }
255    }
256
257    @Override
258    public Widget getActive() {
259        try {
260            return activeWidget;
261        } catch (IndexOutOfBoundsException ex) {
262            return null;
263        }
264    }
265
266    @Override
267    public HandlerRegistration addClearActiveHandler(final ClearActiveHandler handler) {
268        return addHandler(handler, ClearActiveEvent.TYPE);
269    }
270
271    protected CssTypeMixin<CollapsibleType, MaterialCollapsible> getTypeMixin() {
272        if (typeMixin == null) {
273            typeMixin = new CssTypeMixin<>(this);
274        }
275        return typeMixin;
276    }
277}