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.Element;
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.event.logical.shared.HasSelectionHandlers;
028import com.google.gwt.event.logical.shared.SelectionEvent;
029import com.google.gwt.event.logical.shared.SelectionHandler;
030import com.google.gwt.event.shared.HandlerRegistration;
031import com.google.gwt.uibinder.client.UiConstructor;
032import com.google.gwt.user.client.DOM;
033import com.google.gwt.user.client.ui.UIObject;
034import com.google.gwt.user.client.ui.Widget;
035import gwt.material.design.client.base.HasWaves;
036import gwt.material.design.client.base.helper.DOMHelper;
037import gwt.material.design.client.constants.Alignment;
038import gwt.material.design.client.ui.html.ListItem;
039import gwt.material.design.client.ui.html.UnorderedList;
040
041import java.util.ArrayList;
042import java.util.List;
043
044//@formatter:off
045
046/**
047 * You can add dropdown easily by specifying it's item
048 * content and add a UiHandler on it to implement any event.
049 *
050 * <h3>UiBinder Usage:</h3>
051 * <pre>
052 *{@code
053 *<m:MaterialDropDown>
054 *   <m:MaterialLink text="First" />
055 *   <m:MaterialLink text="Second" />
056 *   <m:MaterialLink text="Third" />
057 * </m:MaterialDropDown>
058 * }
059 * </pre>
060 * @author kevzlou7979
061 * @author Ben Dol
062 * @see <a href="http://gwt-material-demo.herokuapp.com/#dropdowns">Material DropDowns</a>
063 */
064//@formatter:on
065public class MaterialDropDown extends UnorderedList implements HasSelectionHandlers<Widget> {
066
067    private String activator;
068    private Element activatorElem;
069
070    // Options
071    private int inDuration = 300;
072    private int outDuration = 225;
073    private boolean constrainWidth = true;
074    private boolean hover = false;
075    private boolean belowOrigin = false;
076    private int gutter = 0;
077    private String alignment = Alignment.LEFT.getCssName();
078    private List<Widget> children = new ArrayList<>();
079
080    public MaterialDropDown() {
081        setInitialClasses("dropdown-content");
082        setId(DOM.createUniqueId());
083    }
084
085    /**
086     * Material Dropdown - adds a list item selection when button, link, icon button pressed.
087     * @param activator - data-activates attribute name of your dropdown activator.
088     */
089    @UiConstructor
090    public MaterialDropDown(String activator) {
091        this();
092        this.activator = activator;
093
094        getElement().setId(this.activator);
095    }
096
097    public MaterialDropDown(Element activatorElem) {
098        this();
099        activatorElem.setAttribute("data-activates", getId());
100        this.activatorElem = activatorElem;
101    }
102
103    public MaterialDropDown(UIObject activator) {
104        this(activator.getElement());
105    }
106
107    @Override
108    protected void onLoad() {
109        super.onLoad();
110
111        initialize();
112    }
113
114    /**
115     * The duration of the transition enter in milliseconds. Default: 300
116     */
117    public void setInDuration(int durationMillis) {
118        this.inDuration = durationMillis;
119    }
120
121    public int getInDuration() {
122        return inDuration;
123    }
124
125    /**
126     * The duration of the transition out in milliseconds. Default: 225
127     */
128    public void setOutDuration(int durationMillis) {
129        this.outDuration = durationMillis;
130    }
131
132    public int getOutDuration() {
133        return outDuration;
134    }
135
136    /**
137     * If true, constrainWidth to the size of the dropdown activator. Default: true
138     */
139    public void setConstrainWidth(boolean constrainWidth) {
140        this.constrainWidth = constrainWidth;
141    }
142
143    public boolean isConstrainWidth() {
144        return constrainWidth;
145    }
146
147    /**
148     * If true, the dropdown will open on hover. Default: false
149     */
150    public void setHover(boolean hover) {
151        this.hover = hover;
152    }
153
154    public boolean isHover() {
155        return hover;
156    }
157
158    /**
159     * This defines the spacing from the aligned edge. Default: 0
160     */
161    public void setGutter(int gutter) {
162        this.gutter = gutter;
163    }
164
165    public int getGutter() {
166        return gutter;
167    }
168
169    /**
170     * If true, the dropdown will show below the activator. Default: false
171     */
172    public void setBelowOrigin(boolean belowOrigin) {
173        this.belowOrigin = belowOrigin;
174    }
175
176    public boolean isBelowOrigin() {
177        return belowOrigin;
178    }
179
180    /**
181     * Defines the edge the menu is aligned to. Default: 'left'
182     */
183    public void setAlignment(Alignment alignment) {
184        this.alignment = alignment.getCssName();
185    }
186
187    public Alignment getAlignment() {
188        return Alignment.fromStyleName(alignment);
189    }
190
191    /**
192     * Get the unique activator set by material widget e.g links, icons, buttons to trigger the dropdown.
193     */
194    public String getActivator() {
195        return activator;
196    }
197
198    /**
199     * Set the unique activator of each dropdown component and it must be unique
200     */
201    public void setActivator(String activator) {
202        this.activator = activator;
203        setId(activator);
204    }
205
206    @Override
207    public void add(final Widget child) {
208        String tagName = child.getElement().getTagName();
209        if(child instanceof ListItem || tagName.toLowerCase().startsWith("li")) {
210            child.getElement().getStyle().setDisplay(Style.Display.BLOCK);
211            add(child, (Element) getElement());
212        } else {
213            ListItem li = new ListItem(child);
214            children.add(child);
215            child.addDomHandler(new ClickHandler() {
216                @Override
217                public void onClick(ClickEvent event) {
218                    SelectionEvent.fire(MaterialDropDown.this, child);
219                }
220            }, ClickEvent.getType());
221
222            // Checks if there are sub dropdown components
223            if(child instanceof MaterialLink) {
224                MaterialLink link = (MaterialLink) child;
225                for(int i = 0; i < link.getWidgetCount(); i++) {
226                    if(link.getWidget(i) instanceof MaterialDropDown) {
227                        link.addClickHandler(new ClickHandler() {
228                            @Override
229                            public void onClick(ClickEvent event) {
230                                event.stopPropagation();
231                            }
232                        });
233                        link.stopTouchStartEvent();
234                    }
235                }
236            }
237
238            if(child instanceof HasWaves) {
239                li.setWaves(((HasWaves) child).getWaves());
240                ((HasWaves) child).setWaves(null);
241            }
242            li.getElement().getStyle().setDisplay(Style.Display.BLOCK);
243            add(li, (Element) getElement());
244        }
245    }
246
247    protected void initialize() {
248        if(activatorElem == null) {
249            activatorElem = DOMHelper.getElementByAttribute("data-activates", activator);
250            if (activatorElem == null) {
251                throw new IllegalStateException("There is no activator element with id: '" + activator
252                        + "' in the DOM, cannot instantiate MaterialDropDown without a data-activates.");
253            }
254        }
255        initialize(activatorElem);
256    }
257
258    /**
259     * Must be called after changing any options.
260     */
261    public void reinitialize() {
262        remove(activatorElem);
263        initialize(activatorElem);
264    }
265
266    /**
267     * Initialize the dropdown components.
268     */
269    protected native void initialize(Element activator)/*-{
270        var that = this;
271        $wnd.jQuery(document).ready(function() {
272            $wnd.jQuery(activator).dropdown({
273                inDuration: that.@gwt.material.design.client.ui.MaterialDropDown::inDuration,
274                outDuration: that.@gwt.material.design.client.ui.MaterialDropDown::outDuration,
275                constrain_width: that.@gwt.material.design.client.ui.MaterialDropDown::constrainWidth, // Does not change width of dropdown to that of the activator
276                hover: that.@gwt.material.design.client.ui.MaterialDropDown::hover, // Activate on hover
277                gutter: that.@gwt.material.design.client.ui.MaterialDropDown::gutter, // Spacing from edge
278                belowOrigin: that.@gwt.material.design.client.ui.MaterialDropDown::belowOrigin, // Displays dropdown below the button
279                alignment: that.@gwt.material.design.client.ui.MaterialDropDown::alignment // Displays dropdown with edge aligned to the left of button
280            });
281        });
282    }-*/;
283
284    protected native void remove(Element activator)/*-{
285        $wnd.jQuery(activator).dropdown("remove");
286    }-*/;
287
288    @Override
289    public HandlerRegistration addSelectionHandler(final SelectionHandler<Widget> handler) {
290        return addHandler(new SelectionHandler<Widget>() {
291            @Override
292            public void onSelection(SelectionEvent<Widget> event) {
293                if(isEnabled()){
294                    handler.onSelection(event);
295                }
296            }
297        }, SelectionEvent.getType());
298    }
299
300    public List<Widget> getItems() {
301        return children;
302    }
303}