001package gwt.material.design.client.ui;
002
003/*
004 * #%L
005 * GwtMaterial
006 * %%
007 * Copyright (C) 2015 GwtMaterialDesign
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import com.google.gwt.dom.client.Document;
024import com.google.gwt.dom.client.Style;
025import com.google.gwt.event.dom.client.ClickEvent;
026import com.google.gwt.event.dom.client.ClickHandler;
027import com.google.gwt.user.client.Window;
028import com.google.gwt.user.client.ui.HasWidgets;
029import com.google.gwt.user.client.ui.Widget;
030import gwt.material.design.client.base.MaterialWidget;
031import gwt.material.design.client.ui.MaterialCollapsible.HasCollapsibleParent;
032import gwt.material.design.client.ui.html.ListItem;
033import gwt.material.design.client.ui.html.UnorderedList;
034
035//@formatter:off
036
037/**
038* CollapsibleItem element to define the body 
039* @author kevzlou7979
040* @author Ben Dol
041* @see <a href="http://gwt-material-demo.herokuapp.com/#collapsibles">Material Collapsibles</a>
042*/
043//@formatter:on
044public class MaterialCollapsibleBody extends MaterialWidget implements HasCollapsibleParent {
045
046    private MaterialCollapsible parent;
047
048    /**
049     * Creates empty collapsible body.
050     */
051    public MaterialCollapsibleBody() {
052        super(Document.get().createDivElement(), "collapsible-body");
053    }
054
055    /**
056     * Add other components into collapsible body.
057     */
058    public MaterialCollapsibleBody(final Widget... widgets) {
059        this();
060        for(Widget w : widgets) {
061            add(w);
062        }
063    }
064
065    @Override
066    public void setParent(MaterialCollapsible parent) {
067        this.parent = parent;
068
069        for(Widget child : this) {
070            checkActiveState(child);
071
072            if(child instanceof HasCollapsibleParent) {
073                ((HasCollapsibleParent) child).setParent(parent);
074            }
075        }
076    }
077
078    @Override
079    public void add(final Widget child) {
080        if(child instanceof UnorderedList) {
081            for(Widget w : (UnorderedList) child) {
082                if(w instanceof ListItem) {
083                    w.getElement().getStyle().setDisplay(Style.Display.BLOCK);
084
085                    provideActiveClickHandler(w);
086                }
087            }
088        } else if(child instanceof ListItem) {
089            child.getElement().getStyle().setDisplay(Style.Display.BLOCK);
090
091            provideActiveClickHandler(child);
092        }
093        super.add(child);
094    }
095
096    @Override
097    public boolean remove(Widget w) {
098        if(w instanceof HasCollapsibleParent) {
099            ((HasCollapsibleParent)w).setParent(null);
100        }
101        return super.remove(w);
102    }
103
104    public void makeActive(Widget child) {
105        parent.clearActive();
106
107        // mark the collapsible item as active
108        MaterialCollapsibleItem item = findCollapsibleItemParent(child);
109        if(item != null) {
110            item.expand();
111        }
112
113        child.addStyleName("active");
114    }
115
116    protected void provideActiveClickHandler(final Widget child) {
117        // Active click handler
118        child.addDomHandler(new ClickHandler() {
119            @Override
120            public void onClick(ClickEvent event) {
121                makeActive(child);
122            }
123        }, ClickEvent.getType());
124    }
125
126    /**
127     * Checks if this child holds the current active state.
128     * If the child is or contains the active state it is applied.
129     */
130    protected void checkActiveState(Widget child) {
131        // Check if this widget has a valid href
132        String href = child.getElement().getAttribute("href");
133        String url = Window.Location.getHref();
134        int pos = url.indexOf("#");
135        String location = pos >= 0 ? url.substring(pos, url.length()) : "";
136
137        if(!href.isEmpty() && location.startsWith(href)) {
138            ListItem li = findListItemParent(child);
139            if(li != null) {
140                makeActive(li);
141            }
142        } else if(child instanceof HasWidgets) {
143            // Recursive check
144            for(Widget w : (HasWidgets)child) {
145                checkActiveState(w);
146            }
147        }
148    }
149
150    protected MaterialCollapsibleItem findCollapsibleItemParent(Widget widget) {
151        if(widget != null) {
152            if (widget instanceof MaterialCollapsibleItem) {
153                return (MaterialCollapsibleItem) widget;
154            } else {
155                return findCollapsibleItemParent(widget.getParent());
156            }
157        }
158        return null;
159    }
160
161    protected ListItem findListItemParent(Widget widget) {
162        if(widget != null) {
163            if (widget instanceof ListItem) {
164                return (ListItem) widget;
165            } else {
166                return findListItemParent(widget.getParent());
167            }
168        }
169        return null;
170    }
171}