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.HasActive;
024import org.gwtbootstrap3.client.ui.base.HasIcon;
025import org.gwtbootstrap3.client.ui.base.HasIconPosition;
026import org.gwtbootstrap3.client.ui.base.HasSize;
027import org.gwtbootstrap3.client.ui.base.HasType;
028import org.gwtbootstrap3.client.ui.base.helper.StyleHelper;
029import org.gwtbootstrap3.client.ui.base.mixin.ActiveMixin;
030import org.gwtbootstrap3.client.ui.constants.ButtonSize;
031import org.gwtbootstrap3.client.ui.constants.ButtonType;
032import org.gwtbootstrap3.client.ui.constants.IconFlip;
033import org.gwtbootstrap3.client.ui.constants.IconPosition;
034import org.gwtbootstrap3.client.ui.constants.IconRotate;
035import org.gwtbootstrap3.client.ui.constants.IconSize;
036import org.gwtbootstrap3.client.ui.constants.IconType;
037import org.gwtbootstrap3.client.ui.constants.Styles;
038
039import com.google.gwt.core.client.Scheduler;
040import com.google.gwt.core.client.Scheduler.ScheduledCommand;
041import com.google.gwt.dom.client.Document;
042import com.google.gwt.dom.client.InputElement;
043import com.google.gwt.event.dom.client.ClickEvent;
044import com.google.gwt.event.dom.client.ClickHandler;
045import com.google.gwt.event.logical.shared.ValueChangeEvent;
046import com.google.gwt.i18n.client.HasDirection.Direction;
047import com.google.gwt.i18n.shared.DirectionEstimator;
048import com.google.gwt.safehtml.shared.SafeHtml;
049import com.google.gwt.uibinder.client.UiConstructor;
050import com.google.gwt.user.client.DOM;
051import com.google.gwt.user.client.Event;
052
053/**
054 * Button representing a radio button used within a {@link ButtonGroup} that has
055 * toggle set to {@code Toogle.BUTTONS}.
056 * <p/>
057 * If you are looking for a classic radio button see {@link RadioButton}.
058 *
059 * @author Sven Jacobs
060 */
061public class RadioButton extends Radio implements HasActive,
062        HasType<ButtonType>, HasSize<ButtonSize>, HasIcon, HasIconPosition {
063
064    private final ActiveMixin<RadioButton> activeMixin = new ActiveMixin<RadioButton>(this);
065
066    private IconPosition iconPosition = IconPosition.LEFT;
067    private Icon icon;
068
069
070    /**
071     * Creates a new radio associated with a particular group, and initialized
072     * with the given HTML label. All radio buttons associated with the same
073     * group name belong to a mutually-exclusive set.
074     * 
075     * Radio buttons are grouped by their name attribute, so changing their name
076     * using the setName() method will also change their associated group.
077     * 
078     * @param name
079     *            the group name with which to associate the radio button
080     * @param label
081     *            this radio button's html label
082     */
083    public RadioButton(String name, SafeHtml label) {
084        this(name, label.asString(), true);
085    }
086
087    /**
088     * @see #RadioButtonToggle(String, SafeHtml)
089     * 
090     * @param name
091     *            the group name with which to associate the radio button
092     * @param label
093     *            this radio button's html label
094     * @param dir
095     *            the text's direction. Note that {@code DEFAULT} means
096     *            direction should be inherited from the widget's parent
097     *            element.
098     */
099    public RadioButton(String name, SafeHtml label, Direction dir) {
100        this(name);
101        setHTML(label, dir);
102    }
103
104    /**
105     * @see #RadioButtonToggle(String, SafeHtml)
106     * 
107     * @param name
108     *            the group name with which to associate the radio button
109     * @param label
110     *            this radio button's html label
111     * @param directionEstimator
112     *            A DirectionEstimator object used for automatic direction
113     *            adjustment. For convenience,
114     *            {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
115     */
116    public RadioButton(String name, SafeHtml label, DirectionEstimator directionEstimator) {
117        this(name);
118        setDirectionEstimator(directionEstimator);
119        setHTML(label.asString());
120    }
121
122    /**
123     * Creates a new radio associated with a particular group, and initialized
124     * with the given HTML label. All radio buttons associated with the same
125     * group name belong to a mutually-exclusive set.
126     * 
127     * Radio buttons are grouped by their name attribute, so changing their name
128     * using the setName() method will also change their associated group.
129     * 
130     * @param name
131     *            the group name with which to associate the radio button
132     * @param label
133     *            this radio button's label
134     */
135    public RadioButton(String name, String label) {
136        this(name);
137        setText(label);
138    }
139
140    /**
141     * @see #RadioButtonToggle(String, SafeHtml)
142     * 
143     * @param name
144     *            the group name with which to associate the radio button
145     * @param label
146     *            this radio button's label
147     * @param dir
148     *            the text's direction. Note that {@code DEFAULT} means
149     *            direction should be inherited from the widget's parent
150     *            element.
151     */
152    public RadioButton(String name, String label, Direction dir) {
153        this(name);
154        setText(label, dir);
155    }
156
157    /**
158     * @see #RadioButtonToggle(String, SafeHtml)
159     * 
160     * @param name
161     *            the group name with which to associate the radio button
162     * @param label
163     *            this radio button's label
164     * @param directionEstimator
165     *            A DirectionEstimator object used for automatic direction
166     *            adjustment. For convenience,
167     *            {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
168     */
169    public RadioButton(String name, String label, DirectionEstimator directionEstimator) {
170        this(name);
171        setDirectionEstimator(directionEstimator);
172        setText(label);
173    }
174
175    /**
176     * Creates a new radio button associated with a particular group, and
177     * initialized with the given label (optionally treated as HTML). All radio
178     * buttons associated with the same group name belong to a
179     * mutually-exclusive set.
180     * 
181     * Radio buttons are grouped by their name attribute, so changing their name
182     * using the setName() method will also change their associated group.
183     * 
184     * @param name
185     *            name the group with which to associate the radio button
186     * @param label
187     *            this radio button's label
188     * @param asHTML
189     *            <code>true</code> to treat the specified label as HTML
190     */
191    public RadioButton(String name, String label, boolean asHTML) {
192        this(name);
193        if (asHTML) {
194            setHTML(label);
195        } else {
196            setText(label);
197        }
198    }
199
200    @UiConstructor
201    public RadioButton(String name) {
202        this(Document.get().createRadioInputElement(name));
203    }
204
205    protected RadioButton(InputElement element) {
206        super(DOM.createLabel(), element);
207
208        setStyleName(Styles.BTN);
209        setType(ButtonType.DEFAULT);
210
211        getElement().appendChild(inputElem);
212        getElement().appendChild(Document.get().createTextNode(" "));
213        getElement().appendChild(labelElem);
214        getElement().appendChild(Document.get().createTextNode(" "));
215    }
216    
217    @Override
218    protected void ensureDomEventHandlers() {
219        // Use a ClickHandler since Bootstrap's jQuery does not trigger native
220        // change events:
221        // http://learn.jquery.com/events/triggering-event-handlers/
222        addClickHandler(new ClickHandler() {
223
224            @Override
225            public void onClick(ClickEvent event) {
226                final boolean oldValue = getValue();
227
228                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
229                    @Override
230                    public void execute() {
231                        ValueChangeEvent.fireIfNotEqual(RadioButton.this,
232                                oldValue, getValue());
233                    }
234                });
235            }
236
237        });
238    }
239
240    @Override
241    public void sinkEvents(int eventBitsToAdd) {
242        // Sink on the actual element because that's what gets clicked
243        if (isOrWasAttached()) {
244            Event.sinkEvents(getElement(),
245                    eventBitsToAdd | Event.getEventsSunk(getElement()));
246        } else {
247            super.sinkEvents(eventBitsToAdd);
248        }
249    }
250
251    @Override
252    public void setSize(ButtonSize size) {
253        StyleHelper.addUniqueEnumStyleName(this, ButtonSize.class, size);
254    }
255
256    @Override
257    public ButtonSize getSize() {
258        return ButtonSize.fromStyleName(getStyleName());
259    }
260
261    @Override
262    public void setType(ButtonType type) {
263        StyleHelper.addUniqueEnumStyleName(this, ButtonType.class, type);
264    }
265
266    @Override
267    public ButtonType getType() {
268        return ButtonType.fromStyleName(getStyleName());
269    }
270
271    @Override
272    public void setActive(boolean active) {
273        setValue(active);
274        activeMixin.setActive(active);
275    }
276
277    @Override
278    public boolean isActive() {
279        return activeMixin.isActive();
280    }
281
282    @Override
283    public void setIconPosition(IconPosition iconPosition) {
284        this.iconPosition = iconPosition;
285        render();
286    }
287
288    @Override
289    public IconPosition getIconPosition() {
290        return iconPosition;
291    }
292
293    @Override
294    public void setIcon(IconType iconType) {
295        getActualIcon().setType(iconType);
296    }
297
298    @Override
299    public IconType getIcon() {
300        return getActualIcon().getType();
301    }
302
303    @Override
304    public void setIconSize(IconSize iconSize) {
305        getActualIcon().setSize(iconSize);
306    }
307
308    @Override
309    public IconSize getIconSize() {
310        return getActualIcon().getSize();
311    }
312
313    @Override
314    public void setIconFlip(IconFlip iconFlip) {
315        getActualIcon().setFlip(iconFlip);
316    }
317
318    @Override
319    public IconFlip getIconFlip() {
320        return getActualIcon().getFlip();
321    }
322
323    @Override
324    public void setIconRotate(IconRotate iconRotate) {
325        getActualIcon().setRotate(iconRotate);
326    }
327
328    @Override
329    public IconRotate getIconRotate() {
330        return getActualIcon().getRotate();
331    }
332
333    @Override
334    public void setIconBordered(boolean iconBordered) {
335        getActualIcon().setBorder(iconBordered);
336    }
337
338    @Override
339    public boolean isIconBordered() {
340        return getActualIcon().isBorder();
341    }
342
343    /** {@inheritDoc} */
344    @Override
345    public void setIconInverse(final boolean iconInverse) {
346        getActualIcon().setInverse(iconInverse);
347    }
348
349    /** {@inheritDoc} */
350    @Override
351    public boolean isIconInverse() {
352        return getActualIcon().isInverse();
353    }
354
355    @Override
356    public void setIconSpin(boolean iconSpin) {
357        getActualIcon().setSpin(iconSpin);
358    }
359
360    @Override
361    public boolean isIconSpin() {
362        return getActualIcon().isSpin();
363    }
364
365    @Override
366    public void setIconPulse(boolean iconPulse) {
367        getActualIcon().setPulse(iconPulse);
368    }
369
370    @Override
371    public boolean isIconPulse() {
372        return getActualIcon().isPulse();
373    }
374
375    @Override
376    public void setIconFixedWidth(boolean iconFixedWidth) {
377        getActualIcon().setFixedWidth(iconFixedWidth);
378    }
379
380    @Override
381    public boolean isIconFixedWidth() {
382        return getActualIcon().isFixedWidth();
383    }
384
385    private Icon getActualIcon() {
386        if (icon == null) {
387            icon = new Icon();
388            render();
389        }
390        return icon;
391    }
392
393    private void render() {
394        if (iconPosition == IconPosition.LEFT) {
395            getElement().insertAfter(icon.getElement(), inputElem);
396        } else {
397            getElement().insertAfter(icon.getElement(), null);
398        }
399    }
400
401}