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 */ 020/* 021 * Copyright 2009 Google Inc. 022 * 023 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 024 * use this file except in compliance with the License. You may obtain a copy of 025 * the License at 026 * 027 * http://www.apache.org/licenses/LICENSE-2.0 028 * 029 * Unless required by applicable law or agreed to in writing, software 030 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 031 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 032 * License for the specific language governing permissions and limitations under 033 * the License. 034 */ 035package gwt.material.design.client.base; 036 037import com.google.gwt.dom.client.Document; 038import com.google.gwt.dom.client.Element; 039import com.google.gwt.dom.client.InputElement; 040import com.google.gwt.dom.client.LabelElement; 041import com.google.gwt.dom.client.Style.WhiteSpace; 042import com.google.gwt.editor.client.IsEditor; 043import com.google.gwt.editor.client.LeafValueEditor; 044import com.google.gwt.editor.client.adapters.TakesValueEditor; 045import com.google.gwt.event.logical.shared.ValueChangeEvent; 046import com.google.gwt.event.logical.shared.ValueChangeHandler; 047import com.google.gwt.event.shared.HandlerRegistration; 048import com.google.gwt.i18n.client.HasDirection.Direction; 049import com.google.gwt.i18n.shared.DirectionEstimator; 050import com.google.gwt.i18n.shared.HasDirectionEstimator; 051import com.google.gwt.safehtml.shared.SafeHtml; 052import com.google.gwt.user.client.DOM; 053import com.google.gwt.user.client.Event; 054import com.google.gwt.user.client.ui.*; 055import gwt.material.design.client.constants.CssName; 056 057/** 058 * A standard check box widget. 059 * <p> 060 * This class also serves as a base class for 061 * {@link RadioButton}. 062 * <p> 063 * <p> 064 * <img class='gallery' src='doc-files/CheckBox.png'/> 065 * </p> 066 * <p> 067 * <p> 068 * <h3>Built-in Bidi Text Support</h3> 069 * This widget is capable of automatically adjusting its direction according to 070 * its content. This feature is controlled by {@link #setDirectionEstimator} or 071 * passing a DirectionEstimator parameter to the constructor, and is off by 072 * default. 073 * </p> 074 * <p> 075 * <h3>CSS Style Rules</h3> 076 * <dl> 077 * <dt>.gwt-CheckBox</dt> 078 * <dd>the outer element</dd> 079 * <dt>.gwt-CheckBox-disabled</dt> 080 * <dd>applied when Checkbox is disabled</dd> 081 * </dl> 082 * <p> 083 * <p> 084 * <h3>Example</h3> 085 * { @example com.google.gwt.examples.CheckBoxExample} 086 * </p> 087 */ 088public class BaseCheckBox extends ButtonBase implements HasName, HasValue<Boolean>, 089 HasWordWrap, HasDirectionalSafeHtml, HasDirectionEstimator, IsEditor<LeafValueEditor<Boolean>> { 090 091 public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = 092 DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR; 093 094 final DirectionalTextHelper directionalTextHelper; 095 InputElement inputElem; 096 LabelElement labelElem; 097 private LeafValueEditor<Boolean> editor; 098 private boolean valueChangeHandlerInitialized; 099 100 /** 101 * Creates a check box with no label. 102 */ 103 public BaseCheckBox() { 104 this(DOM.createSpan()); 105 setStyleName(CssName.GWT_CHECKBOX); 106 } 107 108 /** 109 * Creates a check box with the specified text label. 110 * 111 * @param label the check box's label 112 */ 113 public BaseCheckBox(SafeHtml label) { 114 this(label.asString(), true); 115 } 116 117 /** 118 * Creates a check box with the specified text label. 119 * 120 * @param label the check box's label 121 * @param dir the text's direction. Note that {@code DEFAULT} means direction 122 * should be inherited from the widget's parent element. 123 */ 124 public BaseCheckBox(SafeHtml label, Direction dir) { 125 this(); 126 setHTML(label, dir); 127 } 128 129 /** 130 * Creates a check box with the specified text label. 131 * 132 * @param label the check box's label 133 * @param directionEstimator A DirectionEstimator object used for automatic 134 * direction adjustment. For convenience, 135 * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. 136 */ 137 public BaseCheckBox(SafeHtml label, DirectionEstimator directionEstimator) { 138 this(); 139 setDirectionEstimator(directionEstimator); 140 setHTML(label.asString()); 141 } 142 143 /** 144 * Creates a check box with the specified text label. 145 * 146 * @param label the check box's label 147 */ 148 public BaseCheckBox(String label) { 149 this(); 150 setText(label); 151 } 152 153 /** 154 * Creates a check box with the specified text label. 155 * 156 * @param label the check box's label 157 * @param dir the text's direction. Note that {@code DEFAULT} means direction 158 * should be inherited from the widget's parent element. 159 */ 160 public BaseCheckBox(String label, Direction dir) { 161 this(); 162 setText(label, dir); 163 } 164 165 /** 166 * Creates a label with the specified text and a default direction estimator. 167 * 168 * @param label the check box's label 169 * @param directionEstimator A DirectionEstimator object used for automatic 170 * direction adjustment. For convenience, 171 * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. 172 */ 173 public BaseCheckBox(String label, DirectionEstimator directionEstimator) { 174 this(); 175 setDirectionEstimator(directionEstimator); 176 setText(label); 177 } 178 179 /** 180 * Creates a check box with the specified text label. 181 * 182 * @param label the check box's label 183 * @param asHTML <code>true</code> to treat the specified label as html 184 */ 185 public BaseCheckBox(String label, boolean asHTML) { 186 this(); 187 if (asHTML) { 188 setHTML(label); 189 } else { 190 setText(label); 191 } 192 } 193 194 protected BaseCheckBox(Element elem) { 195 super(elem); 196 197 inputElem = InputElement.as(DOM.createInputCheck()); 198 labelElem = Document.get().createLabelElement(); 199 200 getElement().appendChild(inputElem); 201 getElement().appendChild(labelElem); 202 203 String uid = DOM.createUniqueId(); 204 inputElem.setPropertyString("id", uid); 205 labelElem.setHtmlFor(uid); 206 207 directionalTextHelper = new DirectionalTextHelper(labelElem, true); 208 209 // Accessibility: setting tab index to be 0 by default, ensuring element 210 // appears in tab sequence. FocusWidget's setElement method already 211 // calls setTabIndex, which is overridden below. However, at the time 212 // that this call is made, inputElem has not been created. So, we have 213 // to call setTabIndex again, once inputElem has been created. 214 setTabIndex(0); 215 } 216 217 @Override 218 public HandlerRegistration addValueChangeHandler( 219 final ValueChangeHandler<Boolean> handler) { 220 // Is this the first value change handler? If so, time to add handlers 221 if (!valueChangeHandlerInitialized) { 222 ensureDomEventHandlers(); 223 valueChangeHandlerInitialized = true; 224 } 225 return addHandler(handler, ValueChangeEvent.getType()); 226 } 227 228 @Override 229 public LeafValueEditor<Boolean> asEditor() { 230 if (editor == null) { 231 editor = TakesValueEditor.of(this); 232 } 233 return editor; 234 } 235 236 @Override 237 public DirectionEstimator getDirectionEstimator() { 238 return directionalTextHelper.getDirectionEstimator(); 239 } 240 241 /** 242 * Returns the value property of the input element that backs this widget. 243 * This is the value that will be associated with the CheckBox name and 244 * submitted to the server if a {@link FormPanel} that holds it is submitted 245 * and the box is checked. 246 * <p> 247 * Don't confuse this with {@link #getValue}, which returns true or false if 248 * the widget is checked. 249 */ 250 public String getFormValue() { 251 return inputElem.getValue(); 252 } 253 254 @Override 255 public String getHTML() { 256 return directionalTextHelper.getTextOrHtml(true); 257 } 258 259 @Override 260 public String getName() { 261 return inputElem.getName(); 262 } 263 264 @Override 265 public int getTabIndex() { 266 return inputElem.getTabIndex(); 267 } 268 269 @Override 270 public String getText() { 271 return directionalTextHelper.getTextOrHtml(false); 272 } 273 274 @Override 275 public Direction getTextDirection() { 276 return directionalTextHelper.getTextDirection(); 277 } 278 279 /** 280 * Determines whether this check box is currently checked. 281 * <p> 282 * Note that this <em>does not</em> return the value property of the checkbox 283 * input element wrapped by this widget. For access to that property, see 284 * {@link #getFormValue()} 285 * 286 * @return <code>true</code> if the check box is checked, false otherwise. 287 * Will not return null 288 */ 289 @Override 290 public Boolean getValue() { 291 if (isAttached()) { 292 return inputElem.isChecked(); 293 } else { 294 return inputElem.isDefaultChecked(); 295 } 296 } 297 298 @Override 299 public boolean getWordWrap() { 300 return !WhiteSpace.NOWRAP.getCssName().equals(getElement().getStyle().getWhiteSpace()); 301 } 302 303 /** 304 * Determines whether this check box is currently checked. 305 * 306 * @return <code>true</code> if the check box is checked 307 * @deprecated Use {@link #getValue} instead 308 */ 309 @Deprecated 310 public boolean isChecked() { 311 // Funny comparison b/c getValue could in theory return null 312 return getValue() == true; 313 } 314 315 @Override 316 public boolean isEnabled() { 317 return !inputElem.isDisabled(); 318 } 319 320 @Override 321 public void setAccessKey(char key) { 322 inputElem.setAccessKey("" + key); 323 } 324 325 /** 326 * {@inheritDoc} 327 * <p> 328 * See note at {@link #setDirectionEstimator(DirectionEstimator)}. 329 */ 330 @Override 331 public void setDirectionEstimator(boolean enabled) { 332 directionalTextHelper.setDirectionEstimator(enabled); 333 } 334 335 /** 336 * {@inheritDoc} 337 * <p> 338 * Note: DirectionEstimator should be set before the label has any content; 339 * it's highly recommended to set it using a constructor. Reason: if the 340 * label already has non-empty content, this will update its direction 341 * according to the new estimator's result. This may cause flicker, and thus 342 * should be avoided. 343 */ 344 @Override 345 public void setDirectionEstimator(DirectionEstimator directionEstimator) { 346 directionalTextHelper.setDirectionEstimator(directionEstimator); 347 } 348 349 @Override 350 public void setEnabled(boolean enabled) { 351 inputElem.setDisabled(!enabled); 352 if (enabled) { 353 removeStyleDependentName(CssName.DISABLED); 354 } else { 355 addStyleDependentName(CssName.DISABLED); 356 } 357 } 358 359 @Override 360 public void setFocus(boolean focused) { 361 if (focused) { 362 inputElem.focus(); 363 } else { 364 inputElem.blur(); 365 } 366 } 367 368 /** 369 * Set the value property on the input element that backs this widget. This is 370 * the value that will be associated with the CheckBox's name and submitted to 371 * the server if a {@link FormPanel} that holds it is submitted and the box is 372 * checked. 373 * <p> 374 * Don't confuse this with {@link #setValue}, which actually checks and 375 * unchecks the box. 376 * 377 * @param value 378 */ 379 public void setFormValue(String value) { 380 inputElem.setAttribute("value", value); 381 } 382 383 @Override 384 public void setHTML(SafeHtml html, Direction dir) { 385 directionalTextHelper.setTextOrHtml(html.asString(), dir, true); 386 } 387 388 @Override 389 public void setHTML(String html) { 390 directionalTextHelper.setTextOrHtml(html, true); 391 } 392 393 @Override 394 public void setName(String name) { 395 inputElem.setName(name); 396 } 397 398 @Override 399 public void setTabIndex(int index) { 400 // Need to guard against call to setTabIndex before inputElem is 401 // initialized. This happens because FocusWidget's (a superclass of 402 // CheckBox) setElement method calls setTabIndex before inputElem is 403 // initialized. See CheckBox's protected constructor for more information. 404 if (inputElem != null) { 405 inputElem.setTabIndex(index); 406 } 407 } 408 409 @Override 410 public void setText(String text) { 411 directionalTextHelper.setTextOrHtml(text, false); 412 } 413 414 @Override 415 public void setText(String text, Direction dir) { 416 directionalTextHelper.setTextOrHtml(text, dir, false); 417 } 418 419 /** 420 * Checks or unchecks the check box. 421 * <p> 422 * Note that this <em>does not</em> set the value property of the checkbox 423 * input element wrapped by this widget. For access to that property, see 424 * {@link #setFormValue(String)} 425 * 426 * @param value true to check, false to uncheck; null value implies false 427 */ 428 @Override 429 public void setValue(Boolean value) { 430 setValue(value, false); 431 } 432 433 /** 434 * Checks or unchecks the check box, firing {@link ValueChangeEvent} if 435 * appropriate. 436 * <p> 437 * Note that this <em>does not</em> set the value property of the checkbox 438 * input element wrapped by this widget. For access to that property, see 439 * {@link #setFormValue(String)} 440 * 441 * @param value true to check, false to uncheck; null value implies false 442 * @param fireEvents If true, and value has changed, fire a 443 * {@link ValueChangeEvent} 444 */ 445 @Override 446 public void setValue(Boolean value, boolean fireEvents) { 447 if (value == null) { 448 value = Boolean.FALSE; 449 } 450 451 Boolean oldValue = getValue(); 452 inputElem.setChecked(value); 453 inputElem.setDefaultChecked(value); 454 if (value.equals(oldValue)) { 455 return; 456 } 457 if (fireEvents) { 458 ValueChangeEvent.fire(this, value); 459 } 460 } 461 462 @Override 463 public void setWordWrap(boolean wrap) { 464 getElement().getStyle().setWhiteSpace(wrap ? WhiteSpace.NORMAL : WhiteSpace.NOWRAP); 465 } 466 467 // Unlike other widgets the CheckBox sinks on its inputElement, not 468 // its wrapper 469 @Override 470 public void sinkEvents(int eventBitsToAdd) { 471 if (isOrWasAttached()) { 472 Event.sinkEvents(inputElem, eventBitsToAdd 473 | Event.getEventsSunk(inputElem)); 474 } else { 475 super.sinkEvents(eventBitsToAdd); 476 } 477 } 478 479 protected void ensureDomEventHandlers() { 480 addClickHandler(event -> { 481 // Checkboxes always toggle their value, no need to compare 482 // with old value. Radio buttons are not so lucky, see 483 // overrides in RadioButton 484 ValueChangeEvent.fire(BaseCheckBox.this, getValue()); 485 }); 486 } 487 488 /** 489 * <b>Affected Elements:</b> 490 * <ul> 491 * <li>-label = label next to checkbox.</li> 492 * </ul> 493 * 494 * @see UIObject#onEnsureDebugId(String) 495 */ 496 @Override 497 protected void onEnsureDebugId(String baseID) { 498 super.onEnsureDebugId(baseID); 499 ensureDebugId(labelElem, baseID, "label"); 500 ensureDebugId(inputElem, baseID, "input"); 501 labelElem.setHtmlFor(inputElem.getId()); 502 } 503 504 /** 505 * This method is called when a widget is attached to the browser's document. 506 * onAttach needs special handling for the CheckBox case. Must still call 507 * {@link Widget#onAttach()} to preserve the <code>onAttach</code> contract. 508 */ 509 @Override 510 protected void onLoad() { 511 DOM.setEventListener(inputElem, this); 512 } 513 514 /** 515 * This method is called when a widget is detached from the browser's 516 * document. Overridden because of IE bug that throws away checked state and 517 * in order to clear the event listener off of the <code>inputElem</code>. 518 */ 519 @Override 520 protected void onUnload() { 521 // Clear out the inputElem's event listener (breaking the circular 522 // reference between it and the widget). 523 DOM.setEventListener(inputElem, null); 524 setValue(getValue()); 525 } 526}