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