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.getWidgetCount() > 0) { 119 if (item.getWidget(0) instanceof MaterialWidget) { 120 MaterialWidget child = (MaterialWidget) item.getWidget(0); 121 registerHandler(child.addDomHandler(event -> { 122 SelectionEvent.fire(MaterialDropDown.this, child); 123 }, ClickEvent.getType())); 124 } 125 } 126 } 127 }); 128 } 129 130 @Override 131 protected void onUnload() { 132 super.onUnload(); 133 134 unload(); 135 } 136 137 @Override 138 public void load() { 139 Widget parent = getParent(); 140 if (parent instanceof HasActivates) { 141 String uid = DOM.createUniqueId(); 142 ((HasActivates) parent).setActivates(uid); 143 setId(uid); 144 activatorElement = parent.getElement(); 145 } else if (activatorElement == null) { 146 activatorElement = DOMHelper.getElementByAttribute("data-activates", activator); 147 if (activatorElement == null) { 148 GWT.log("There is no activator element with id: '" + activator + "' in the DOM, " + 149 "cannot instantiate MaterialDropDown without a data-activates.", new IllegalStateException()); 150 } 151 } 152 153 $(activatorElement).dropdown(options); 154 } 155 156 @Override 157 public void unload() { 158 $(activatorElement).dropdown("remove"); 159 } 160 161 @Override 162 public void reload() { 163 unload(); 164 load(); 165 } 166 167 @Override 168 public void add(final Widget child) { 169 String tagName = child.getElement().getTagName(); 170 if (child instanceof ListItem || tagName.toLowerCase().startsWith("li")) { 171 child.getElement().getStyle().setDisplay(Style.Display.BLOCK); 172 add(child, (Element) getElement()); 173 } else { 174 ListItem li = new ListItem(child); 175 // Checks if there are sub dropdown components 176 if (child instanceof MaterialLink) { 177 MaterialLink link = (MaterialLink) child; 178 for (int i = 0; i < link.getWidgetCount(); i++) { 179 if (link.getWidget(i) instanceof MaterialDropDown) { 180 registerHandler(link.addClickHandler(DomEvent::stopPropagation)); 181 link.stopTouchStartEvent(); 182 } 183 } 184 } 185 186 if (child instanceof HasWaves) { 187 li.setWaves(((HasWaves) child).getWaves()); 188 ((HasWaves) child).setWaves(null); 189 } 190 li.getElement().getStyle().setDisplay(Style.Display.BLOCK); 191 add(li, (Element) getElement()); 192 } 193 } 194 195 @Override 196 public void setInDuration(int durationMillis) { 197 options.inDuration = durationMillis; 198 } 199 200 @Override 201 public int getInDuration() { 202 return options.inDuration; 203 } 204 205 @Override 206 public void setOutDuration(int durationMillis) { 207 options.outDuration = durationMillis; 208 } 209 210 @Override 211 public int getOutDuration() { 212 return options.outDuration; 213 } 214 215 /** 216 * If true, constrainWidth to the size of the dropdown activator. Default: true 217 */ 218 public void setConstrainWidth(boolean constrainWidth) { 219 options.constrain_width = constrainWidth; 220 } 221 222 public boolean isConstrainWidth() { 223 return options.constrain_width; 224 } 225 226 /** 227 * If true, the dropdown will open on hover. Default: false 228 */ 229 public void setHover(boolean hover) { 230 options.hover = hover; 231 } 232 233 public boolean isHover() { 234 return options.hover; 235 } 236 237 /** 238 * This defines the spacing from the aligned edge. Default: 0 239 */ 240 public void setGutter(int gutter) { 241 options.gutter = gutter; 242 } 243 244 public int getGutter() { 245 return options.gutter; 246 } 247 248 /** 249 * If true, the dropdown will show below the activator. Default: false 250 */ 251 public void setBelowOrigin(boolean belowOrigin) { 252 options.belowOrigin = belowOrigin; 253 } 254 255 public boolean isBelowOrigin() { 256 return options.belowOrigin; 257 } 258 259 /** 260 * Defines the edge the menu is aligned to. Default: 'left' 261 */ 262 public void setAlignment(Alignment alignment) { 263 options.alignment = alignment.getCssName(); 264 } 265 266 public Alignment getAlignment() { 267 return Alignment.fromStyleName(options.alignment); 268 } 269 270 /** 271 * Get the unique activator set by material widget e.g links, icons, buttons to trigger the dropdown. 272 */ 273 public String getActivator() { 274 return activator; 275 } 276 277 /** 278 * Set the unique activator of each dropdown component and it must be unique 279 */ 280 public void setActivator(String activator) { 281 this.activator = activator; 282 setId(activator); 283 } 284 285 public List<Widget> getItems() { 286 return getChildrenList(); 287 } 288 289 public Element getActivatorElement() { 290 return activatorElement; 291 } 292 293 @Override 294 public HandlerRegistration addSelectionHandler(final SelectionHandler<Widget> handler) { 295 return addHandler((SelectionHandler<Widget>) event -> { 296 Widget widget = event.getSelectedItem(); 297 if (widget instanceof HasEnabled && ((HasEnabled) widget).isEnabled() && isEnabled()) { 298 handler.onSelection(event); 299 } 300 }, SelectionEvent.getType()); 301 } 302}