001/* 002 * Copyright 2012 GWT-Bootstrap 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of 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, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.github.gwtbootstrap.client.ui; 017 018import com.github.gwtbootstrap.client.ui.base.HasId; 019import com.github.gwtbootstrap.client.ui.base.HasStyle; 020import com.github.gwtbootstrap.client.ui.base.IsResponsive; 021import com.github.gwtbootstrap.client.ui.base.IsSearchQuery; 022import com.github.gwtbootstrap.client.ui.base.ResponsiveHelper; 023import com.github.gwtbootstrap.client.ui.base.SearchQueryStyleHelper; 024import com.github.gwtbootstrap.client.ui.base.Style; 025import com.github.gwtbootstrap.client.ui.base.StyleHelper; 026import com.github.gwtbootstrap.client.ui.constants.Constants; 027import com.github.gwtbootstrap.client.ui.constants.Device; 028import com.google.gwt.dom.client.Document; 029import com.google.gwt.dom.client.InputElement; 030import com.google.gwt.dom.client.LabelElement; 031import com.google.gwt.dom.client.SpanElement; 032import com.google.gwt.editor.client.IsEditor; 033import com.google.gwt.editor.client.LeafValueEditor; 034import com.google.gwt.editor.client.adapters.TakesValueEditor; 035import com.google.gwt.event.dom.client.ClickEvent; 036import com.google.gwt.event.dom.client.ClickHandler; 037import com.google.gwt.event.logical.shared.ValueChangeEvent; 038import com.google.gwt.event.logical.shared.ValueChangeHandler; 039import com.google.gwt.event.shared.HandlerRegistration; 040import com.google.gwt.i18n.client.HasDirection.Direction; 041import com.google.gwt.i18n.shared.DirectionEstimator; 042import com.google.gwt.i18n.shared.HasDirectionEstimator; 043import com.google.gwt.safehtml.shared.SafeHtml; 044import com.google.gwt.user.client.DOM; 045import com.google.gwt.user.client.Element; 046import com.google.gwt.user.client.Event; 047import com.google.gwt.user.client.EventListener; 048import com.google.gwt.user.client.ui.ButtonBase; 049import com.google.gwt.user.client.ui.DirectionalTextHelper; 050import com.google.gwt.user.client.ui.FormPanel; 051import com.google.gwt.user.client.ui.HasDirectionalSafeHtml; 052import com.google.gwt.user.client.ui.HasName; 053import com.google.gwt.user.client.ui.HasValue; 054import com.google.gwt.user.client.ui.HasWordWrap; 055import com.google.gwt.user.client.ui.RadioButton; 056import com.google.gwt.user.client.ui.UIObject; 057import com.google.gwt.user.client.ui.Widget; 058 059import static com.github.gwtbootstrap.client.ui.constants.Constants.CHECKBOX; 060import static com.github.gwtbootstrap.client.ui.constants.Constants.RADIO; 061 062/** 063 * CheckBox widgets. 064 * <p> 065 * Re-design for Bootstrap. 066 * </p> 067 * 068 * <p>CheckBox Usage:</p> 069 * <pre>{@code 070 * <b:CheckBox formValue="remember-me" text="Remember me"/> 071 * }</pre> 072 * <p>Above code renders to...</p> 073 * <pre>{@code 074 * <label class="checkbox pull-left" for="gwt-uid-n"> 075 * <input id="gwt-uid-n" type="checkbox" value="remember-me" tabindex="0"></input> 076 * <span>Remember me</span> 077 * </label>}</pre> 078 * ...where n is any number generated by GWT. 079 * @since 2.0.4.0 080 * @author ohashi keisuke 081 */ 082public class CheckBox extends ButtonBase implements HasName, HasValue<Boolean>, HasWordWrap, HasDirectionalSafeHtml, HasDirectionEstimator, IsEditor<LeafValueEditor<Boolean>>, IsSearchQuery, HasId, IsResponsive, HasStyle{ 083 084 public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR; 085 086 final DirectionalTextHelper directionalTextHelper; 087 088 InputElement inputElem; 089 SpanElement spanElem; 090 091 092 private LeafValueEditor<Boolean> editor; 093 094 private boolean valueChangeHandlerInitialized; 095 096 /** 097 * Creates a check box with no label. 098 */ 099 public CheckBox() { 100 this(DOM.createInputCheck()); 101 } 102 103 /** 104 * Creates a check box with the specified text label. 105 * 106 * @param label 107 * the check box's label 108 */ 109 public CheckBox(SafeHtml label) { 110 this(label.asString(), true); 111 } 112 113 /** 114 * Creates a check box with the specified text label. 115 * 116 * @param label 117 * the check box's label 118 * @param dir 119 * the text's direction. Note that {@code DEFAULT} means 120 * direction should be inherited from the widget's parent 121 * element. 122 */ 123 public CheckBox(SafeHtml label, 124 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 133 * the check box's label 134 * @param directionEstimator 135 * A DirectionEstimator object used for automatic direction 136 * adjustment. For convenience, 137 * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. 138 */ 139 public CheckBox(SafeHtml label, 140 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 150 * the check box's label 151 */ 152 public CheckBox(String label) { 153 this(); 154 setText(label); 155 } 156 157 /** 158 * Creates a check box with the specified text label. 159 * 160 * @param label 161 * the check box's label 162 * @param dir 163 * the text's direction. Note that {@code DEFAULT} means 164 * direction should be inherited from the widget's parent 165 * element. 166 */ 167 public CheckBox(String label, 168 Direction dir) { 169 this(); 170 setText(label, dir); 171 } 172 173 /** 174 * Creates a label with the specified text and a default direction 175 * estimator. 176 * 177 * @param label 178 * the check box's label 179 * @param directionEstimator 180 * A DirectionEstimator object used for automatic direction 181 * adjustment. For convenience, 182 * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. 183 */ 184 public CheckBox(String label, 185 DirectionEstimator directionEstimator) { 186 this(); 187 setDirectionEstimator(directionEstimator); 188 setText(label); 189 } 190 191 /** 192 * Creates a check box with the specified text label. 193 * 194 * @param label 195 * the check box's label 196 * @param asHTML 197 * <code>true</code> to treat the specified label as html 198 */ 199 public CheckBox(String label, 200 boolean asHTML) { 201 this(); 202 if (asHTML) { 203 setHTML(label); 204 } else { 205 setText(label); 206 } 207 } 208 209 protected CheckBox(Element elem) { 210 super(DOM.createLabel()); 211 212 assert elem.hasAttribute("type") : "The elem should has type attributes"; 213 214 String elementType = elem.getAttribute("type"); 215 if(CHECKBOX.equalsIgnoreCase(elementType)) { 216 this.setStyleName(CHECKBOX); 217 } else if(RADIO.equalsIgnoreCase(elementType)){ 218 this.setStyleName(RADIO); 219 } 220 221 inputElem = InputElement.as(elem); 222 spanElem = Document.get().createSpanElement(); 223 getElement().appendChild(inputElem); 224 getElement().appendChild(spanElem); 225 226 String uid = DOM.createUniqueId(); 227 inputElem.setPropertyString("id", uid); 228 asLabel().setHtmlFor(uid); 229 directionalTextHelper = new DirectionalTextHelper(spanElem, false); 230 setTabIndex(0); 231 } 232 233 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler) { 234 // Is this the first value change handler? If so, time to add handlers 235 if (!valueChangeHandlerInitialized) { 236 ensureDomEventHandlers(); 237 valueChangeHandlerInitialized = true; 238 } 239 return addHandler(handler, ValueChangeEvent.getType()); 240 } 241 242 public LeafValueEditor<Boolean> asEditor() { 243 if (editor == null) { 244 editor = TakesValueEditor.of(this); 245 } 246 return editor; 247 } 248 249 public DirectionEstimator getDirectionEstimator() { 250 return directionalTextHelper.getDirectionEstimator(); 251 } 252 253 /** 254 * Returns the value property of the input element that backs this widget. 255 * This is the value that will be associated with the CheckBox name and 256 * submitted to the server if a {@link FormPanel} that holds it is submitted 257 * and the box is checked. 258 * <p> 259 * Don't confuse this with {@link #getValue}, which returns true or false if 260 * the widget is checked. 261 */ 262 public String getFormValue() { 263 return inputElem.getValue(); 264 } 265 266 @Override 267 public String getHTML() { 268 return directionalTextHelper.getTextOrHtml(true); 269 } 270 271 public String getName() { 272 return inputElem.getName(); 273 } 274 275 @Override 276 public int getTabIndex() { 277 return inputElem.getTabIndex(); 278 } 279 280 @Override 281 public String getText() { 282 return directionalTextHelper.getTextOrHtml(false); 283 } 284 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 293 * checkbox input element wrapped by this widget. For access to that 294 * property, see {@link #getFormValue()} 295 * 296 * @return <code>true</code> if the check box is checked, false otherwise. 297 * Will not return null 298 */ 299 public Boolean getValue() { 300 if (isAttached()) { 301 return inputElem.isChecked(); 302 } else { 303 return inputElem.isDefaultChecked(); 304 } 305 } 306 307 public boolean getWordWrap() { 308 return !getElement().getStyle().getProperty("whiteSpace").equals("nowrap"); 309 } 310 311 /** 312 * Determines whether this check box is currently checked. 313 * 314 * @return <code>true</code> if the check box is checked 315 * @deprecated Use {@link #getValue} instead 316 */ 317 @Deprecated 318 public boolean isChecked() { 319 // Funny comparison b/c getValue could in theory return null 320 return getValue() == true; 321 } 322 323 @Override 324 public boolean isEnabled() { 325 return !inputElem.isDisabled(); 326 } 327 328 @Override 329 public void setAccessKey(char key) { 330 inputElem.setAccessKey("" + key); 331 } 332 333 /** 334 * Checks or unchecks this check box. Does not fire {@link ValueChangeEvent} 335 * . (If you want the event to fire, use {@link #setValue(Boolean, boolean)} 336 * ) 337 * 338 * @param checked 339 * <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 public void setDirectionEstimator(boolean enabled) { 353 directionalTextHelper.setDirectionEstimator(enabled); 354 } 355 356 /** 357 * {@inheritDoc} 358 * <p> 359 * Note: DirectionEstimator should be set before the label has any content; 360 * it's highly recommended to set it using a constructor. Reason: if the 361 * label already has non-empty content, this will update its direction 362 * according to the new estimator's result. This may cause flicker, and thus 363 * should be avoided. 364 */ 365 public void setDirectionEstimator(DirectionEstimator directionEstimator) { 366 directionalTextHelper.setDirectionEstimator(directionEstimator); 367 } 368 369 @Override 370 public void setEnabled(boolean enabled) { 371 inputElem.setDisabled(!enabled); 372 if (enabled) { 373 inputElem.removeClassName(Constants.DISABLED); 374 } else { 375 inputElem.addClassName(Constants.DISABLED); 376 } 377 } 378 379 @Override 380 public void setFocus(boolean focused) { 381 if (focused) { 382 inputElem.focus(); 383 } else { 384 inputElem.blur(); 385 } 386 } 387 388 /** 389 * Set the value property on the input element that backs this widget. This 390 * is the value that will be associated with the CheckBox's name and 391 * submitted to the server if a {@link FormPanel} that holds it is submitted 392 * and the box is checked. 393 * <p> 394 * Don't confuse this with {@link #setValue}, which actually checks and 395 * unchecks the box. 396 * 397 * @param value 398 */ 399 public void setFormValue(String value) { 400 inputElem.setAttribute("value", value); 401 } 402 403 public void setHTML(SafeHtml html, Direction dir) { 404 directionalTextHelper.setTextOrHtml(html.asString(), dir, true); 405 } 406 407 @Override 408 public void setHTML(String html) { 409 directionalTextHelper.setTextOrHtml(html, true); 410 } 411 412 public void setName(String name) { 413 inputElem.setName(name); 414 } 415 416 @Override 417 public void setTabIndex(int index) { 418 // Need to guard against call to setTabIndex before inputElem is 419 // initialized. This happens because FocusWidget's (a superclass of 420 // CheckBox) setElement method calls setTabIndex before inputElem is 421 // initialized. See CheckBox's protected constructor for more 422 // information. 423 if (inputElem != null) { 424 inputElem.setTabIndex(index); 425 } 426 } 427 428 @Override 429 public void setText(String text) { 430 directionalTextHelper.setTextOrHtml(text, false); 431 } 432 433 public void setText(String text, Direction dir) { 434 directionalTextHelper.setTextOrHtml(text, dir, false); 435 } 436 437 /** 438 * Checks or unchecks the check box. 439 * <p> 440 * Note that this <em>does not</em> set the value property of the checkbox 441 * input element wrapped by this widget. For access to that property, see 442 * {@link #setFormValue(String)} 443 * 444 * @param value 445 * true to check, false to uncheck; null value implies false 446 */ 447 public void setValue(Boolean value) { 448 setValue(value, false); 449 } 450 451 /** 452 * Checks or unchecks the check box, firing {@link ValueChangeEvent} if 453 * appropriate. 454 * <p> 455 * Note that this <em>does not</em> set the value property of the checkbox 456 * input element wrapped by this widget. For access to that property, see 457 * {@link #setFormValue(String)} 458 * 459 * @param value 460 * true to check, false to uncheck; null value implies false 461 * @param fireEvents 462 * If true, and value has changed, fire a 463 * {@link ValueChangeEvent} 464 */ 465 public void setValue(Boolean value, boolean fireEvents) { 466 if (value == null) { 467 value = Boolean.FALSE; 468 } 469 470 Boolean oldValue = getValue(); 471 inputElem.setChecked(value); 472 inputElem.setDefaultChecked(value); 473 if (value.equals(oldValue)) { 474 return; 475 } 476 if (fireEvents) { 477 ValueChangeEvent.fire(this, value); 478 } 479 } 480 481 public void setWordWrap(boolean wrap) { 482 getElement().getStyle().setProperty("whiteSpace", wrap 483 ? "normal" 484 : "nowrap"); 485 } 486 487 // Unlike other widgets the CheckBox sinks on its inputElement, not 488 // its wrapper 489 @Override 490 public void sinkEvents(int eventBitsToAdd) { 491 if (isOrWasAttached()) { 492 Event.sinkEvents(inputElem, eventBitsToAdd | Event.getEventsSunk(inputElem)); 493 } else { 494 super.sinkEvents(eventBitsToAdd); 495 } 496 } 497 498 protected void ensureDomEventHandlers() { 499 addClickHandler(new ClickHandler() { 500 501 public void onClick(ClickEvent event) { 502 // Checkboxes always toggle their value, no need to compare 503 // with old value. Radio buttons are not so lucky, see 504 // overrides in RadioButton 505 ValueChangeEvent.fire(CheckBox.this, getValue()); 506 } 507 }); 508 } 509 510 /** 511 * <b>Affected Elements:</b> 512 * <ul> 513 * <li>-label = label next to checkbox.</li> 514 * </ul> 515 * 516 * @see UIObject#onEnsureDebugId(String) 517 */ 518 @Override 519 protected void onEnsureDebugId(String baseID) { 520 super.onEnsureDebugId(baseID); 521 ensureDebugId(asLabel(), baseID, "label"); 522 ensureDebugId(inputElem, baseID, "input"); 523 asLabel().setHtmlFor(inputElem.getId()); 524 } 525 526 /** 527 * This method is called when a widget is attached to the browser's 528 * document. onAttach needs special handling for the CheckBox case. Must 529 * still call {@link Widget#onAttach()} to preserve the 530 * <code>onAttach</code> contract. 531 */ 532 @Override 533 protected void onLoad() { 534 setEventListener(inputElem, this); 535 } 536 537 /** 538 * This method is called when a widget is detached from the browser's 539 * document. Overridden because of IE bug that throws away checked state and 540 * in order to clear the event listener off of the <code>inputElem</code>. 541 */ 542 @Override 543 protected void onUnload() { 544 // Clear out the inputElem's event listener (breaking the circular 545 // reference between it and the widget). 546 setEventListener(asOld(inputElem), null); 547 setValue(getValue()); 548 } 549 550 /** 551 * Replace the current input element with a new one. Preserves all state 552 * except for the name property, for nasty reasons related to radio button 553 * grouping. (See implementation of {@link RadioButton#setName}.) 554 * 555 * @param elem 556 * the new input element 557 */ 558 protected void replaceInputElement(Element elem) { 559 InputElement newInputElem = InputElement.as(elem); 560 // Collect information we need to set 561 int tabIndex = getTabIndex(); 562 boolean checked = getValue(); 563 boolean enabled = isEnabled(); 564 String formValue = getFormValue(); 565 String uid = inputElem.getId(); 566 String accessKey = inputElem.getAccessKey(); 567 int sunkEvents = Event.getEventsSunk(inputElem); 568 569 // Clear out the old input element 570 setEventListener(asOld(inputElem), null); 571 572 getElement().replaceChild(newInputElem, inputElem); 573 574 // Sink events on the new element 575 Event.sinkEvents(elem, Event.getEventsSunk(inputElem)); 576 Event.sinkEvents(inputElem, 0); 577 inputElem = newInputElem; 578 579 // Setup the new element 580 Event.sinkEvents(inputElem, sunkEvents); 581 inputElem.setId(uid); 582 if (!"".equals(accessKey)) { 583 inputElem.setAccessKey(accessKey); 584 } 585 setTabIndex(tabIndex); 586 setValue(checked); 587 setEnabled(enabled); 588 setFormValue(formValue); 589 590 // Set the event listener 591 if (isAttached()) { 592 setEventListener(asOld(inputElem), this); 593 } 594 } 595 596 private Element asOld(com.google.gwt.dom.client.Element elem) { 597 Element oldSchool = elem.cast(); 598 return oldSchool; 599 } 600 601 private void setEventListener(com.google.gwt.dom.client.Element e, EventListener listener) { 602 DOM.setEventListener(asOld(e), listener); 603 } 604 605 protected LabelElement asLabel() { 606 return LabelElement.as(getElement()); 607 } 608 609 public void setInline(boolean inline) { 610 if(getStyleName().contains(Constants.INLINE)) { 611 removeStyleName(Constants.INLINE); 612 } 613 614 if(inline) { 615 addStyleName(Constants.INLINE); 616 } 617 } 618 619 /** 620 * {@inheritDoc} 621 */ 622 @Override 623 public void setSearchQuery(boolean searchQuery) { 624 SearchQueryStyleHelper.setSearchQuery(inputElem, searchQuery); 625 } 626 627 /** 628 * {@inheritDoc} 629 */ 630 @Override 631 public boolean isSearchQuery() { 632 return SearchQueryStyleHelper.isSearchQuery(inputElem); 633 } 634 635 /** 636 * {@inheritDoc} 637 */ 638 @Override 639 public String getId() { 640 return inputElem.getId(); 641 } 642 643 /** 644 * {@inheritDoc} 645 */ 646 @Override 647 public void setId(String id) { 648 inputElem.setId(id); 649 asLabel().setHtmlFor(id); 650 } 651 652 653 /** 654 * {@inheritDoc} 655 */ 656 @Override 657 public void setShowOn(Device device) { 658 ResponsiveHelper.setShowOn(this, device); 659 } 660 661 /** 662 * {@inheritDoc} 663 */ 664 @Override 665 public void setHideOn(Device device) { 666 ResponsiveHelper.setHideOn(this, device); 667 668 } 669 670 /** 671 * {@inheritDoc} 672 */ 673 @Override 674 public void setStyle(Style style) { 675 StyleHelper.setStyle(this, style); 676 } 677 678 /** 679 * {@inheritDoc} 680 */ 681 @Override 682 public void addStyle(Style style) { 683 StyleHelper.addStyle(this, style); 684 } 685 686 /** 687 * {@inheritDoc} 688 */ 689 @Override 690 public void removeStyle(Style style) { 691 StyleHelper.removeStyle(this, style); 692 693 } 694}