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    }