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    }