001 package 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
023 import org.gwtbootstrap3.client.ui.base.HasFormValue;
024 import org.gwtbootstrap3.client.ui.constants.Styles;
025 import org.gwtbootstrap3.client.ui.gwt.ButtonBase;
026 import org.gwtbootstrap3.client.ui.gwt.FormPanel;
027 import org.gwtbootstrap3.client.ui.gwt.Widget;
028 import org.gwtbootstrap3.client.ui.impl.CheckBoxImpl;
029
030 import com.google.gwt.core.shared.GWT;
031 import com.google.gwt.dom.client.Document;
032 import com.google.gwt.dom.client.Element;
033 import com.google.gwt.dom.client.InputElement;
034 import com.google.gwt.dom.client.LabelElement;
035 import com.google.gwt.dom.client.SpanElement;
036 import com.google.gwt.dom.client.Style.WhiteSpace;
037 import com.google.gwt.editor.client.IsEditor;
038 import com.google.gwt.editor.client.LeafValueEditor;
039 import com.google.gwt.editor.client.adapters.TakesValueEditor;
040 import com.google.gwt.event.dom.client.ChangeEvent;
041 import com.google.gwt.event.dom.client.ChangeHandler;
042 import com.google.gwt.event.dom.client.HasChangeHandlers;
043 import com.google.gwt.event.logical.shared.ValueChangeEvent;
044 import com.google.gwt.event.logical.shared.ValueChangeHandler;
045 import com.google.gwt.event.shared.HandlerRegistration;
046 import com.google.gwt.i18n.client.HasDirection.Direction;
047 import com.google.gwt.i18n.shared.DirectionEstimator;
048 import com.google.gwt.i18n.shared.HasDirectionEstimator;
049 import com.google.gwt.safehtml.shared.SafeHtml;
050 import com.google.gwt.user.client.DOM;
051 import com.google.gwt.user.client.Event;
052 import com.google.gwt.user.client.ui.DirectionalTextHelper;
053 import com.google.gwt.user.client.ui.HasDirectionalSafeHtml;
054 import com.google.gwt.user.client.ui.HasName;
055 import com.google.gwt.user.client.ui.HasValue;
056 import com.google.gwt.user.client.ui.HasWordWrap;
057 import 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 */
072 public 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 }