aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java235
-rw-r--r--client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java122
-rw-r--r--server/src/main/java/com/vaadin/ui/RadioButtonGroup.java236
-rw-r--r--server/src/test/java/com/vaadin/ui/RadioButtonGroupBoVTest.java46
-rw-r--r--server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java91
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupConstants.java30
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupState.java36
-rw-r--r--uitest-common/src/main/java/com/vaadin/testbench/customelements/RadioButtonGroupElement.java105
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java75
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java112
10 files changed, 1088 insertions, 0 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java b/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java
new file mode 100644
index 0000000000..893935d562
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HasEnabled;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.WidgetUtil;
+import com.vaadin.shared.Registration;
+import com.vaadin.shared.data.DataCommunicatorConstants;
+import com.vaadin.shared.ui.optiongroup.RadioButtonGroupConstants;
+import elemental.json.JsonObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import static com.vaadin.shared.ui.optiongroup.RadioButtonGroupConstants.JSONKEY_ITEM_DISABLED;
+
+/**
+ * The client-side widget for the {@code RadioButtonGroup} component.
+ *
+ * @author Vaadin Ltd.
+ * @since 8.0
+ */
+public class VRadioButtonGroup extends Composite implements Field, ClickHandler,
+ com.vaadin.client.Focusable, HasEnabled {
+
+ public static final String CLASSNAME = "v-select-optiongroup";
+ public static final String CLASSNAME_OPTION = "v-select-option";
+
+ private final Map<RadioButton, JsonObject> optionsToItems;
+ private final Map<String, RadioButton> keyToOptions;
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public ApplicationConnection client;
+
+ /**
+ * Widget holding the different options (e.g. ListBox or Panel for radio
+ * buttons) (optional, fallbacks to container Panel)
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public Panel optionsContainer;
+
+ private boolean htmlContentAllowed = false;
+
+ private boolean enabled;
+ private boolean readonly;
+ private final String groupId;
+ private List<Consumer<JsonObject>> selectionChangeListeners;
+
+ public VRadioButtonGroup() {
+ groupId = DOM.createUniqueId();
+ optionsContainer = new FlowPanel();
+ initWidget(this.optionsContainer);
+ optionsContainer.setStyleName(CLASSNAME);
+ optionsToItems = new HashMap<>();
+ keyToOptions = new HashMap<>();
+ selectionChangeListeners = new ArrayList<>();
+ }
+
+ /*
+ * Build all the options
+ */
+ public void buildOptions(List<JsonObject> items) {
+ /*
+ * In order to retain focus, we need to update values rather than
+ * recreate panel from scratch (#10451). However, the panel will be
+ * rebuilt (losing focus) if number of elements or their order is
+ * changed.
+ */
+
+ Roles.getRadiogroupRole().set(getElement());
+ optionsContainer.clear();
+ optionsToItems.clear();
+ keyToOptions.clear();
+ for (JsonObject item : items) {
+ String itemHtml = item
+ .getString(RadioButtonGroupConstants.JSONKEY_ITEM_VALUE);
+ if (!isHtmlContentAllowed()) {
+ itemHtml = WidgetUtil.escapeHTML(itemHtml);
+ }
+ RadioButton radioButton = new RadioButton(groupId);
+
+ String iconUrl = item
+ .getString(RadioButtonGroupConstants.JSONKEY_ITEM_ICON);
+ if (iconUrl != null && iconUrl.length() != 0) {
+ Icon icon = client.getIcon(iconUrl);
+ itemHtml = icon.getElement().getString() + itemHtml;
+ }
+ radioButton.setStyleName("v-radiobutton");
+ radioButton.addStyleName(CLASSNAME_OPTION);
+ radioButton.addClickHandler(this);
+ radioButton.setHTML(itemHtml);
+ radioButton.setValue(item
+ .getBoolean(RadioButtonGroupConstants.JSONKEY_ITEM_SELECTED));
+ boolean optionEnabled = !item.getBoolean(JSONKEY_ITEM_DISABLED);
+ boolean enabled = optionEnabled && !isReadonly() && isEnabled();
+ radioButton.setEnabled(enabled);
+ String key = item.getString(DataCommunicatorConstants.KEY);
+
+ optionsContainer.add(radioButton);
+ optionsToItems.put(radioButton, item);
+ keyToOptions.put(key, radioButton);
+ }
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (event.getSource() instanceof RadioButton) {
+ RadioButton source = (RadioButton) event.getSource();
+ if (!source.isEnabled()) {
+ // Click events on the text are received even though the
+ // radiobutton is disabled
+ return;
+ }
+ if (BrowserInfo.get().isWebkit()) {
+ // Webkit does not focus non-text input elements on click
+ // (#11854)
+ source.setFocus(true);
+ }
+
+ JsonObject item = optionsToItems.get(source);
+ assert item != null;
+
+ new ArrayList<>(selectionChangeListeners)
+ .forEach(listener -> listener.accept(item));
+ }
+ }
+
+ public void setTabIndex(int tabIndex) {
+ for (Widget anOptionsContainer : optionsContainer) {
+ FocusWidget widget = (FocusWidget) anOptionsContainer;
+ widget.setTabIndex(tabIndex);
+ }
+ }
+
+ protected void updateEnabledState() {
+ boolean radioButtonEnabled = isEnabled() && !isReadonly();
+ // sets options enabled according to the widget's enabled,
+ // readonly and each options own enabled
+ for (Map.Entry<RadioButton, JsonObject> entry : optionsToItems
+ .entrySet()) {
+ RadioButton radioButton = entry.getKey();
+ JsonObject value = entry.getValue();
+ Boolean isOptionEnabled = !value
+ .getBoolean(RadioButtonGroupConstants.JSONKEY_ITEM_DISABLED);
+ radioButton.setEnabled(radioButtonEnabled && isOptionEnabled);
+ }
+ }
+
+ @Override
+ public void focus() {
+ Iterator<Widget> iterator = optionsContainer.iterator();
+ if (iterator.hasNext()) {
+ ((Focusable) iterator.next()).setFocus(true);
+ }
+ }
+
+ public boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public boolean isReadonly() {
+ return readonly;
+ }
+
+ public void setReadonly(boolean readonly) {
+ if (this.readonly != readonly) {
+ this.readonly = readonly;
+ updateEnabledState();
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (this.enabled != enabled) {
+ this.enabled = enabled;
+ updateEnabledState();
+ }
+ }
+
+ public Registration addSelectionChangeHandler(
+ Consumer<JsonObject> selectionChanged) {
+ selectionChangeListeners.add(selectionChanged);
+ return (Registration) () -> selectionChangeListeners
+ .remove(selectionChanged);
+ }
+
+ public void selectItemKey(String selectedItemKey) {
+ RadioButton radioButton = keyToOptions.get(selectedItemKey);
+ assert radioButton!=null;
+ radioButton.setValue(true);
+ }
+}
diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java
new file mode 100644
index 0000000000..135126a099
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui.optiongroup;
+
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.connectors.AbstractListingConnector;
+import com.vaadin.client.data.DataSource;
+import com.vaadin.client.ui.VRadioButtonGroup;
+import com.vaadin.shared.Range;
+import com.vaadin.shared.Registration;
+import com.vaadin.shared.data.selection.SelectionModel;
+import com.vaadin.shared.data.selection.SelectionServerRpc;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState;
+import com.vaadin.ui.RadioButtonGroup;
+import elemental.json.JsonObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Connect(RadioButtonGroup.class)
+public class RadioButtonGroupConnector
+ extends AbstractListingConnector<SelectionModel.Single<?>> {
+
+ private Registration selectionChangeRegistration;
+ private Registration dataChangeRegistration;
+
+ private final SelectionServerRpc selectionRpc = getRpcProxy(
+ SelectionServerRpc.class);
+
+ @Override
+ protected void init() {
+ super.init();
+
+ selectionChangeRegistration = getWidget().addSelectionChangeHandler(
+ e -> selectionRpc.select(getRowKey(e)));
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+ selectionChangeRegistration.remove();
+ selectionChangeRegistration = null;
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+ getWidget().client = getConnection();
+ }
+
+ @Override
+ public void setDataSource(DataSource<JsonObject> dataSource) {
+ if (dataChangeRegistration != null) {
+ dataChangeRegistration.remove();
+ }
+ dataChangeRegistration = dataSource
+ .addDataChangeHandler(this::onDataChange);
+ super.setDataSource(dataSource);
+ }
+
+ @OnStateChange("readOnly")
+ @SuppressWarnings("deprecation")
+ void updateWidgetReadOnly() {
+ getWidget().setEnabled(isEnabled() && !isReadOnly());
+ }
+
+ @OnStateChange("selectedItemKey")
+ void updateSelectedItem() {
+ getWidget().selectItemKey(getState().selectedItemKey);
+ }
+
+ @Override
+ public VRadioButtonGroup getWidget() {
+ return (VRadioButtonGroup) super.getWidget();
+ }
+
+ @Override
+ public RadioButtonGroupState getState() {
+ return (RadioButtonGroupState) super.getState();
+ }
+
+ /**
+ * A data change handler registered to the data source. Updates the data
+ * items and selection status when the data source notifies of new changes
+ * from the server side.
+ *
+ * @param range
+ * the new range of data items
+ */
+ private void onDataChange(Range range) {
+ assert range.getStart() == 0 && range.getEnd() == getDataSource()
+ .size() : "RadioButtonGroup only supports full updates, but " +
+ "got range "
+ + range;
+
+ final VRadioButtonGroup select = getWidget();
+ DataSource<JsonObject> dataSource = getDataSource();
+ int size = dataSource.size();
+ List<JsonObject> options = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ options.add(dataSource.getRow(i));
+ }
+ select.buildOptions(options);
+ updateSelectedItem();
+ }
+}
diff --git a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java
new file mode 100644
index 0000000000..e3cc1892b0
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.Listing;
+import com.vaadin.server.Resource;
+import com.vaadin.server.ResourceReference;
+import com.vaadin.server.data.DataGenerator;
+import com.vaadin.server.data.DataSource;
+import com.vaadin.shared.ui.optiongroup.RadioButtonGroupConstants;
+import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState;
+import elemental.json.JsonObject;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * A group of RadioButtons. Individual radiobuttons are made from items supplied by
+ * a {@link DataSource}. RadioButtons may have captions and icons.
+ *
+ * @param <T>
+ * item type
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public class RadioButtonGroup<T> extends AbstractSingleSelect<T> {
+
+ private Function<T, Resource> itemIconProvider = item -> null;
+
+ private Function<T, String> itemCaptionProvider = String::valueOf;
+
+ private Predicate<T> itemEnabledProvider = item -> true;
+
+ /**
+ * Constructs a new RadioButtonGroup with caption.
+ *
+ * @param caption
+ * caption text
+ * @see Listing#setDataSource(DataSource)
+ */
+ public RadioButtonGroup(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new RadioButtonGroup with caption and DataSource.
+ *
+ * @param caption
+ * the caption text
+ * @param dataSource
+ * the data source, not null
+ * @see Listing#setDataSource(DataSource)
+ */
+ public RadioButtonGroup(String caption, DataSource<T> dataSource) {
+ this(caption);
+ setDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a new RadioButtonGroup with caption and DataSource containing
+ * given items.
+ *
+ * @param caption
+ * the caption text
+ * @param items
+ * the data items to use, not null
+ * @see Listing#setDataSource(DataSource)
+ */
+ public RadioButtonGroup(String caption, Collection<T> items) {
+ this(caption, DataSource.create(items));
+ }
+
+ /**
+ * Constructs a new RadioButtonGroup.
+ *
+ * @see Listing#setDataSource(DataSource)
+ */
+ public RadioButtonGroup() {
+ setSelectionModel(new SimpleSingleSelection());
+
+ addDataGenerator(new DataGenerator<T>() {
+ @Override
+ public void generateData(T data, JsonObject jsonObject) {
+ jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_VALUE,
+ itemCaptionProvider.apply(data));
+ Resource icon = itemIconProvider.apply(data);
+ if (icon != null) {
+ String iconUrl = ResourceReference
+ .create(icon, RadioButtonGroup.this, null).getURL();
+ jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_ICON,
+ iconUrl);
+ }
+ if (!itemEnabledProvider.test(data)) {
+ jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_DISABLED,
+ true);
+ }
+
+ if (getSelectionModel().isSelected(data)) {
+ jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_SELECTED,
+ true);
+ }
+ }
+
+ @Override
+ public void destroyData(T data) {
+ }
+ });
+
+ }
+
+ /**
+ * Sets whether html is allowed in the item captions. If set to true, the
+ * captions are passed to the browser as html and the developer is
+ * responsible for ensuring no harmful html is used. If set to false, the
+ * content is passed to the browser as plain text.
+ *
+ * @param htmlContentAllowed
+ * true if the captions are used as html, false if used as plain
+ * text
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ getState().htmlContentAllowed = htmlContentAllowed;
+ }
+
+ /**
+ * Checks whether captions are interpreted as html or plain text.
+ *
+ * @return true if the captions are used as html, false if used as plain
+ * text
+ * @see #setHtmlContentAllowed(boolean)
+ */
+ public boolean isHtmlContentAllowed() {
+ return getState(false).htmlContentAllowed;
+ }
+
+ @Override
+ protected RadioButtonGroupState getState() {
+ return (RadioButtonGroupState) super.getState();
+ }
+
+ @Override
+ protected RadioButtonGroupState getState(boolean markAsDirty) {
+ return (RadioButtonGroupState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Returns the item icons provider.
+ *
+ * @return the icons provider for items
+ * @see #setItemIconProvider
+ */
+ public Function<T, Resource> getItemIconProvider() {
+ return itemIconProvider;
+ }
+
+ /**
+ * Sets the item icon provider for this radiobutton group. The icon provider is
+ * queried for each item to optionally display an icon next to the item
+ * caption. If the provider returns null for an item, no icon is displayed.
+ * The default provider always returns null (no icons).
+ *
+ * @param itemIconProvider
+ * icons provider, not null
+ */
+ public void setItemIconProvider(Function<T, Resource> itemIconProvider) {
+ Objects.requireNonNull(itemIconProvider);
+ this.itemIconProvider = itemIconProvider;
+ }
+
+ /**
+ * Returns the item caption provider.
+ *
+ * @return the captions provider
+ * @see #setItemCaptionProvider
+ */
+ public Function<T, String> getItemCaptionProvider() {
+ return itemCaptionProvider;
+ }
+
+ /**
+ * Sets the item caption provider for this radiobutton group. The caption
+ * provider is queried for each item to optionally display an item textual
+ * representation. The default provider returns
+ * {@code String.valueOf(item)}.
+ *
+ * @param itemCaptionProvider
+ * the item caption provider, not null
+ */
+ public void setItemCaptionProvider(
+ Function<T, String> itemCaptionProvider) {
+ Objects.requireNonNull(itemCaptionProvider);
+ this.itemCaptionProvider = itemCaptionProvider;
+ }
+
+ /**
+ * Returns the item enabled predicate.
+ *
+ * @return the item enabled predicate
+ * @see #setItemEnabledProvider
+ */
+ public Predicate<T> getItemEnabledProvider() {
+ return itemEnabledProvider;
+ }
+
+ /**
+ * Sets the item enabled predicate for this radiobutton group. The predicate is
+ * applied to each item to determine whether the item should be enabled
+ * (true) or disabled (false). Disabled items are displayed as grayed out
+ * and the user cannot select them. The default predicate always returns
+ * true (all the items are enabled).
+ *
+ * @param itemEnabledProvider
+ * the item enable predicate, not null
+ */
+ public void setItemEnabledProvider(Predicate<T> itemEnabledProvider) {
+ Objects.requireNonNull(itemEnabledProvider);
+ this.itemEnabledProvider = itemEnabledProvider;
+ }
+}
diff --git a/server/src/test/java/com/vaadin/ui/RadioButtonGroupBoVTest.java b/server/src/test/java/com/vaadin/ui/RadioButtonGroupBoVTest.java
new file mode 100644
index 0000000000..2ec2404515
--- /dev/null
+++ b/server/src/test/java/com/vaadin/ui/RadioButtonGroupBoVTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import java.util.EnumSet;
+
+/**
+ * Option group test from Book of Vaadin
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public class RadioButtonGroupBoVTest
+{
+ public enum Status {
+ STATE_A,
+ STATE_B,
+ STATE_C,
+ STATE_D;
+
+ public String getCaption() {
+ return "** " + toString();
+ }
+ }
+
+
+ public void createOptionGroup() {
+ RadioButtonGroup<Status> s = new RadioButtonGroup<>();
+ s.setItems(EnumSet.allOf(Status.class));
+ s.setItemCaptionProvider(Status::getCaption);
+ }
+
+}
diff --git a/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java b/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java
new file mode 100644
index 0000000000..a03da685d8
--- /dev/null
+++ b/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import com.vaadin.server.data.DataSource;
+import com.vaadin.shared.data.selection.SelectionModel;
+import com.vaadin.shared.data.selection.SelectionModel.Multi;
+import com.vaadin.shared.data.selection.SelectionServerRpc;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class RadioButtonGroupTest {
+ private RadioButtonGroup<String> radioButtonGroup;
+ private SelectionModel.Single<String> selectionModel;
+
+ @Before
+ public void setUp() {
+ radioButtonGroup = new RadioButtonGroup<>();
+ // Intentional deviation from upcoming selection order
+ radioButtonGroup
+ .setDataSource(DataSource.create("Third", "Second", "First"));
+ selectionModel = radioButtonGroup.getSelectionModel();
+ }
+
+
+ @Test
+ public void apiSelectionChange_notUserOriginated() {
+ AtomicInteger listenerCount = new AtomicInteger(0);
+
+ radioButtonGroup.addSelectionListener(event -> {
+ listenerCount.incrementAndGet();
+ Assert.assertFalse(event.isUserOriginated());
+ });
+
+ radioButtonGroup.select("First");
+ radioButtonGroup.select("Second");
+
+ radioButtonGroup.deselect("Second");
+ radioButtonGroup.getSelectionModel().deselectAll();
+
+ Assert.assertEquals(3, listenerCount.get());
+ }
+
+ @Test
+ public void rpcSelectionChange_userOriginated() {
+ AtomicInteger listenerCount = new AtomicInteger(0);
+
+ radioButtonGroup.addSelectionListener(event -> {
+ listenerCount.incrementAndGet();
+ Assert.assertTrue(event.isUserOriginated());
+ });
+
+ SelectionServerRpc rpc = ComponentTest.getRpcProxy(radioButtonGroup,
+ SelectionServerRpc.class);
+
+ rpc.select(getItemKey("First"));
+ rpc.select(getItemKey("Second"));
+ rpc.deselect(getItemKey("Second"));
+
+ Assert.assertEquals(3, listenerCount.get());
+ }
+
+ private String getItemKey(String dataObject) {
+ return radioButtonGroup.getDataCommunicator().getKeyMapper()
+ .key(dataObject);
+ }
+
+ private static void assertSelectionOrder(Multi<String> selectionModel,
+ String... selectionOrder) {
+ Assert.assertEquals(Arrays.asList(selectionOrder),
+ new ArrayList<>(selectionModel.getSelectedItems()));
+ }
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupConstants.java b/shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupConstants.java
new file mode 100644
index 0000000000..5278d211de
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupConstants.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.optiongroup;
+
+import java.io.Serializable;
+
+public class RadioButtonGroupConstants implements Serializable {
+ public static final String JSONKEY_ITEM_DISABLED = "d";
+
+ public static final String JSONKEY_ITEM_ICON = "i";
+
+ public static final String JSONKEY_ITEM_VALUE = "v";
+
+ public static final String JSONKEY_ITEM_KEY = "k";
+
+ public static final String JSONKEY_ITEM_SELECTED = "s";
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupState.java b/shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupState.java
new file mode 100644
index 0000000000..bdd007613c
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/optiongroup/RadioButtonGroupState.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.optiongroup;
+
+import com.vaadin.shared.AbstractFieldState;
+import com.vaadin.shared.annotations.DelegateToWidget;
+import com.vaadin.shared.ui.AbstractSingleSelectState;
+
+/**
+ * Shared state for the RadioButtonGroup component.
+ *
+ * @author Vaadin Ltd.
+ * @since 8.0
+ */
+public class RadioButtonGroupState extends AbstractSingleSelectState {
+
+ {
+ primaryStyleName = "v-select-optiongroup";
+ }
+
+ @DelegateToWidget
+ public boolean htmlContentAllowed = false;
+}
diff --git a/uitest-common/src/main/java/com/vaadin/testbench/customelements/RadioButtonGroupElement.java b/uitest-common/src/main/java/com/vaadin/testbench/customelements/RadioButtonGroupElement.java
new file mode 100644
index 0000000000..f6bb4d93f7
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/testbench/customelements/RadioButtonGroupElement.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.testbench.customelements;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.AbstractSelectElement;
+import com.vaadin.testbench.elementsbase.ServerClass;
+import org.openqa.selenium.WebElement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TestBench element supporting RadioButtonGroup
+ *
+ * @author Vaadin Ltd
+ */
+
+@ServerClass("com.vaadin.ui.RadioButtonGroup")
+public class RadioButtonGroupElement extends AbstractSelectElement {
+ private static org.openqa.selenium.By byButtonSpan = By
+ .className("v-select-option");
+ private static org.openqa.selenium.By byLabel = By.tagName("label");
+ private static org.openqa.selenium.By byInput = By.tagName("input");
+
+ public List<String> getOptions() {
+ List<String> optionTexts = new ArrayList<>();
+ List<WebElement> options = findElements(byButtonSpan);
+ for (WebElement option : options) {
+ optionTexts.add(option.findElement(byLabel).getText());
+ }
+ return optionTexts;
+ }
+
+ public void selectByText(String text) throws ReadOnlyException {
+ if (isReadOnly()) {
+ throw new ReadOnlyException();
+ }
+ List<WebElement> options = findElements(byButtonSpan);
+ for (int i = 0; i < options.size(); i++) {
+ WebElement option = options.get(i);
+ if (text.equals(option.findElement(byLabel).getText())) {
+ option.findElement(byInput).click();
+
+ // Seems like this is needed because of #19753
+ waitForVaadin();
+
+ // Toggling selection causes the DOM to be rebuilt, so fetch new
+ // items and continue iterating from the same index
+ options = findElements(byButtonSpan);
+ }
+ }
+ }
+
+ /**
+ * Return list of the selected options in the radiobutton group
+ *
+ * @return list of the selected options in the radiobutton group
+ */
+ public List<String> getSelection() {
+ List<String> values = new ArrayList<>();
+ List<WebElement> options = findElements(byButtonSpan);
+ for (WebElement option : options) {
+ WebElement checkedItem;
+ checkedItem = option.findElement(By.tagName("input"));
+ String checked = checkedItem.getAttribute("checked");
+ if (checked != null
+ && checkedItem.getAttribute("checked").equals("true")) {
+ values.add(option.findElement(By.tagName("label")).getText());
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Select option in the radiobutton group with the specified value
+ *
+ * @param chars
+ * value of the option in the radiobutton group which will be
+ * selected
+ */
+ public void selectOption(CharSequence chars) throws ReadOnlyException {
+ selectByText((String) chars);
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException(
+ "Clear operation is not supported for RadioButtonGroup."
+ + " This operation has no effect on the element.");
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java b/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java
new file mode 100644
index 0000000000..1b4e9ac0df
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.radiobutton;
+
+import com.vaadin.shared.data.selection.SelectionModel;
+import com.vaadin.tests.components.abstractlisting.AbstractListingTestUI;
+import com.vaadin.ui.RadioButtonGroup;
+
+import java.util.stream.IntStream;
+
+/**
+ * Test UI for RadioButtonGroup component
+ *
+ * @author Vaadin Ltd
+ */
+public class RadioButtonGroupTestUI
+ extends AbstractListingTestUI<RadioButtonGroup<Object>> {
+
+ private final String selectionCategory = "Selection";
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ protected Class<RadioButtonGroup<Object>> getTestClass() {
+ return (Class) RadioButtonGroup.class;
+ }
+
+ @Override
+ protected void createActions() {
+ super.createActions();
+ createListenerMenu();
+ createSelectionMenu();
+ }
+
+ protected void createSelectionMenu() {
+ createClickAction(
+ "Clear selection", selectionCategory, (component, item,
+ data) -> component.getSelectionModel().deselectAll(),
+ "");
+
+ Command<RadioButtonGroup<Object>, String> toggleSelection = (component,
+ item, data) -> toggleSelection(item);
+
+ IntStream.of(0, 1, 5, 10, 25).mapToObj(i -> "Item " + i)
+ .forEach(item -> createClickAction("Toggle " + item, selectionCategory,
+ toggleSelection, item));
+ }
+
+ private void toggleSelection(String item) {
+ SelectionModel.Single<Object> selectionModel = getComponent().getSelectionModel();
+ if (selectionModel.isSelected(item)) {
+ selectionModel.deselect(item);
+ } else {
+ selectionModel.select(item);
+ }
+ }
+
+ protected void createListenerMenu() {
+ createListenerAction("Selection listener", "Listeners",
+ c -> c.addSelectionListener(
+ e -> log("Selected: " + e.getSelectedItem())));
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java b/uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java
new file mode 100644
index 0000000000..a591924641
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.radiobutton;
+
+import com.vaadin.testbench.customelements.RadioButtonGroupElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test for RadioButtonGroup
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public class RadioButtonGroupTest extends MultiBrowserTest {
+
+ @Before
+ public void setUp() throws Exception {
+ openTestURL();
+ }
+
+ @Test
+ public void initialLoad_containsCorrectItems() {
+ assertItems(20);
+ }
+
+ @Test
+ public void initialItems_reduceItemCount_containsCorrectItems() {
+ selectMenuPath("Component", "Data source", "Items", "5");
+ assertItems(5);
+ }
+
+ @Test
+ public void initialItems_increaseItemCount_containsCorrectItems() {
+ selectMenuPath("Component", "Data source", "Items", "100");
+ assertItems(100);
+ }
+
+ @Test
+ public void clickToSelect() {
+ selectMenuPath("Component", "Listeners", "Selection listener");
+
+ getSelect().selectByText("Item 4");
+ Assert.assertEquals("1. Selected: Optional[Item 4]", getLogRow(0));
+
+ getSelect().selectByText("Item 2");
+ Assert.assertEquals("2. Selected: Optional[Item 2]", getLogRow(0));
+
+ getSelect().selectByText("Item 4");
+ Assert.assertEquals("3. Selected: Optional[Item 4]", getLogRow(0));
+ }
+
+ @Test
+ public void selectProgramatically() {
+ selectMenuPath("Component", "Listeners", "Selection listener");
+
+ selectMenuPath("Component", "Selection", "Toggle Item 5");
+ Assert.assertEquals("2. Selected: Optional[Item 5]", getLogRow(0));
+ assertSelected("Item 5");
+
+ selectMenuPath("Component", "Selection", "Toggle Item 1");
+ Assert.assertEquals("4. Selected: Optional[Item 1]", getLogRow(0));
+ // DOM order
+ assertSelected("Item 1");
+
+ selectMenuPath("Component", "Selection", "Toggle Item 5");
+ Assert.assertEquals("6. Selected: Optional[Item 5]", getLogRow(0));
+ assertSelected("Item 5");
+ }
+
+ private void assertSelected(String... expectedSelection) {
+ Assert.assertEquals(Arrays.asList(expectedSelection),
+ getSelect().getSelection());
+ }
+
+ @Override
+ protected Class<?> getUIClass() {
+ return RadioButtonGroupTestUI.class;
+ }
+
+ protected RadioButtonGroupElement getSelect() {
+ return $(RadioButtonGroupElement.class).first();
+ }
+
+ protected void assertItems(int count) {
+ int i = 0;
+ for (String text : getSelect().getOptions()) {
+ assertEquals("Item " + i, text);
+ i++;
+ }
+ assertEquals("Number of items", count, i);
+ }
+}