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.dom.client.Element; 023import com.google.gwt.event.logical.shared.AttachEvent; 024import com.google.gwt.user.client.ui.*; 025import gwt.material.design.client.base.HasId; 026import gwt.material.design.client.base.HasPosition; 027import gwt.material.design.client.base.HasReload; 028import gwt.material.design.client.base.JsLoader; 029import gwt.material.design.client.base.helper.EventHelper; 030import gwt.material.design.client.constants.Position; 031import gwt.material.design.client.js.JsTooltipOptions; 032 033import java.util.Iterator; 034import java.util.NoSuchElementException; 035 036import static gwt.material.design.client.js.JsMaterialElement.$; 037 038/** 039 * Basic implementation for the Material Design tooltip. 040 * <h3>UiBinder Example</h3> 041 * <pre> 042 * {@code 043 * <m:MaterialTooltip text="..."> 044 * ... 045 * </b:MaterialTooltip> 046 * } 047 * </pre> 048 * 049 * @author kevzlou7979 050 * @author Ben Dol 051 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#dialogs">Material Tooltip</a> 052 * @see <a href="https://material.io/guidelines/components/tooltips.html">Material Design Specification</a> 053 */ 054public class MaterialTooltip implements JsLoader, IsWidget, HasWidgets, HasOneWidget, HasId, HasText, HasPosition, HasReload { 055 056 private String id; 057 private String html; 058 private Widget widget; 059 private JsTooltipOptions options = JsTooltipOptions.create(); 060 061 /** 062 * Creates the empty Tooltip 063 */ 064 public MaterialTooltip() { 065 } 066 067 /** 068 * Creates the tooltip around this widget 069 * 070 * @param w widget for the tooltip 071 */ 072 public MaterialTooltip(final Widget w) { 073 setWidget(w); 074 } 075 076 /** 077 * Creates the tooltip around this widget with given title 078 * 079 * @param w widget for the tooltip 080 * @param text text for the tooltip 081 */ 082 public MaterialTooltip(final Widget w, final String text) { 083 setWidget(w); 084 setText(text); 085 } 086 087 @Override 088 public void load() { 089 $(widget.getElement()).tooltip(options); 090 } 091 092 @Override 093 public void unload() { 094 command("remove"); 095 } 096 097 @Override 098 public void reload() { 099 unload(); 100 load(); 101 } 102 103 /** 104 * Force the Tooltip to be destroyed 105 * 106 * @deprecated use {@link #unload()} 107 */ 108 @Deprecated 109 public void remove() { 110 unload(); 111 } 112 113 @Override 114 public void clear() { 115 widget = null; 116 } 117 118 @Override 119 public Iterator<Widget> iterator() { 120 // Simple iterator for the widget 121 return new Iterator<Widget>() { 122 boolean hasElement = widget != null; 123 Widget returned = null; 124 125 @Override 126 public boolean hasNext() { 127 return hasElement; 128 } 129 130 @Override 131 public Widget next() { 132 if (!hasElement || (widget == null)) { 133 throw new NoSuchElementException(); 134 } 135 hasElement = false; 136 return (returned = widget); 137 } 138 139 @Override 140 public void remove() { 141 if (returned != null) { 142 MaterialTooltip.this.remove(returned); 143 } 144 } 145 }; 146 } 147 148 @Override 149 public boolean remove(final Widget w) { 150 // Validate. 151 if (widget != w) { 152 return false; 153 } 154 155 // Logical detach. 156 clear(); 157 return true; 158 } 159 160 @Override 161 public Widget asWidget() { 162 return widget; 163 } 164 165 @Override 166 public String toString() { 167 return asWidget().toString(); 168 } 169 170 protected void command(String command) { 171 if (widget != null) { 172 $(widget.getElement()).tooltip(command); 173 } 174 } 175 176 @Override 177 public void setWidget(final Widget widget) { 178 // Validate 179 if (widget == this.widget) { 180 return; 181 } 182 183 // Remove old child 184 if (this.widget != null) { 185 remove(this.widget); 186 } 187 188 // Logical attach, but don't physical attach; done by jquery. 189 this.widget = widget; 190 if (this.widget == null) { 191 return; 192 } 193 194 setAttribute("data-delay", String.valueOf(options.delay)); 195 196 if (options.position != null) { 197 setAttribute("data-position", options.position); 198 } 199 200 if (options.tooltip != null) { 201 setAttribute("data-tooltip", options.tooltip); 202 } 203 204 if (this.widget.isAttached()) { 205 reload(); 206 } else { 207 // Smart detect the attachment and detachment of widget to update the tooltip 208 widget.addAttachHandler(event -> { 209 if (event.isAttached()) { 210 // If its attached - reload the tooltip 211 reload(); 212 } else { 213 // If it was detached - unload the tooltip 214 unload(); 215 } 216 }); 217 } 218 } 219 220 @Override 221 public void add(final Widget child) { 222 if (getWidget() != null) { 223 throw new IllegalStateException("Can only contain one child widget"); 224 } 225 setWidget(child); 226 } 227 228 @Override 229 public void setWidget(final IsWidget w) { 230 setWidget(w.asWidget()); 231 } 232 233 @Override 234 public Widget getWidget() { 235 return widget; 236 } 237 238 @Override 239 public void setId(final String id) { 240 this.id = id; 241 if (widget != null) { 242 widget.getElement().setId(id); 243 } 244 } 245 246 @Override 247 public String getId() { 248 return (widget == null) ? id : widget.getElement().getId(); 249 } 250 251 @Override 252 public void setPosition(final Position position) { 253 options.position = position.getCssName(); 254 255 setAttribute("data-position", position.getCssName()); 256 } 257 258 @Override 259 public Position getPosition() { 260 return options.position != null ? Position.fromStyleName(options.position) : null; 261 } 262 263 public void setDelayMs(final int delayMs) { 264 options.delay = delayMs; 265 266 setAttribute("data-delay", String.valueOf(delayMs)); 267 } 268 269 public int getDelayMs() { 270 return options.delay; 271 } 272 273 /** 274 * Gets the tooltip's display string 275 * 276 * @return String tooltip display string 277 */ 278 @Override 279 public String getText() { 280 return options.tooltip; 281 } 282 283 /** 284 * Sets the tooltip's display string 285 * 286 * @param text String display string 287 */ 288 @Override 289 public void setText(final String text) { 290 options.tooltip = text; 291 292 setAttribute("data-tooltip", text); 293 } 294 295 /** 296 * @deprecated Use {@link #getHtml} 297 */ 298 @Deprecated 299 public String getTooltipHTML() { 300 return getHtml(); 301 } 302 303 /** 304 * @deprecated Use {@link #setHtml} 305 */ 306 @Deprecated 307 public void setTooltipHTML(String html) { 308 setHtml(html); 309 } 310 311 /** 312 * Get the html of the tooltip. 313 */ 314 public String getHtml() { 315 return html; 316 } 317 318 /** 319 * Set the html as value inside the tooltip. 320 */ 321 public void setHtml(String html) { 322 this.html = html; 323 324 Element element = widget.getElement(); 325 if (widget.isAttached()) { 326 $("#" + element.getAttribute("data-tooltip-id")) 327 .find("span") 328 .html(html != null ? html : ""); 329 } else { 330 widget.addAttachHandler(event -> 331 $("#" + element.getAttribute("data-tooltip-id")) 332 .find("span") 333 .html(html != null ? html : "")); 334 } 335 } 336 337 public void setAttribute(String attr, String value) { 338 if (widget != null) { 339 AttachEvent.Handler handler = event -> { 340 widget.getElement().setAttribute(attr, value); 341 }; 342 if (widget.isAttached()) { 343 handler.onAttachOrDetach(null); 344 } else { 345 EventHelper.onAttachOnce(widget, handler); 346 } 347 } 348 } 349}