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}