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 }