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