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