From 05c051717401660a857ee7a314361a1735f376e6 Mon Sep 17 00:00:00 2001 From: elmot Date: Wed, 14 Sep 2016 17:48:50 +0300 Subject: Create a RadioButtonGroup that replaces the single select case of OptionGroup Change-Id: I56b0f1dfa889e2eaa3db9b0b0aac860f1bb4dea8 --- .../com/vaadin/client/ui/VRadioButtonGroup.java | 235 +++++++++++++++++++++ .../ui/optiongroup/RadioButtonGroupConnector.java | 122 +++++++++++ 2 files changed, 357 insertions(+) create mode 100644 client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java create mode 100644 client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java (limited to 'client') 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 optionsToItems; + private final Map 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) + *

+ * 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> 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 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 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 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 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> { + + 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 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 dataSource = getDataSource(); + int size = dataSource.size(); + List options = new ArrayList<>(); + for (int i = 0; i < size; i++) { + options.add(dataSource.getRow(i)); + } + select.buildOptions(options); + updateSelectedItem(); + } +} -- cgit v1.2.3