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