Skip to content

Commit

Permalink
TwinColSelect with new databinding API
Browse files Browse the repository at this point in the history
Removes feature for adding new items.
Introduces a AbstractMultiSelect-abstraction layer,
which is used in server side by TwinColSelect & CheckBoxGroup and
on client side only TwinColSelect for now. Plan is to use it for
ListSelect too.

Further improvement would be to make AbstractMultiSelect use
SelectionModel that extends AbstractSelectionModel and is thus used
as an extension both as client & server side.

Updates to JUnit 4.12 for easier use of @parameterized test..

Change-Id: I64258c2229b9514d382693748e2ca562a1e448d4
  • Loading branch information
pleku authored and Vaadin Code Review committed Sep 27, 2016
1 parent 0052d59 commit 7e78d52
Show file tree
Hide file tree
Showing 26 changed files with 2,403 additions and 474 deletions.
@@ -0,0 +1,182 @@
/*
* 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.connectors;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.data.DataSource;
import com.vaadin.shared.Range;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.selection.MultiSelectServerRpc;
import com.vaadin.shared.data.selection.SelectionModel;
import com.vaadin.shared.ui.ListingJsonConstants;

import elemental.json.JsonObject;

/**
* A base connector class for multiselects.
* <p>
* Does not care about the framework provided selection model for now, instead
* just passes selection information per item.
*
* @author Vaadin Ltd.
*
* @since 8.0
*/
public abstract class AbstractMultiSelectConnector
extends AbstractListingConnector<SelectionModel.Multi<?>> {

/**
* Abstraction layer to help populate different multiselect widgets based on
* same JSON data.
*/
public interface MultiSelectWidget {

/**
* Sets the given items to the select.
*
* @param items
* the items for the select
*/
void setItems(List<JsonObject> items);

/**
* Adds a selection change listener the select.
*
* @param selectionChangeListener
* the listener to add, not {@code null}
* @return a registration handle to remove the listener
*/
Registration addSelectionChangeListener(
BiConsumer<Set<String>, Set<String>> selectionChangeListener);

/**
* Returns the caption for the given item.
*
* @param item
* the item, not {@code null}
* @return caption of the item
*/
static String getCaption(JsonObject item) {
return item.getString(ListingJsonConstants.JSONKEY_ITEM_VALUE);
}

/**
* Returns the key for the given item.
*
* @param item
* the item, not {@code null}
* @return key of the item
*/
static String getKey(JsonObject item) {
return getRowKey(item);
}

/**
* Returns whether the given item is enabled or not.
* <p>
* Disabling items is not supported by all multiselects.
*
* @param item
* the item, not {@code null}
* @return {@code true} enabled, {@code false} if not
*/
static boolean isEnabled(JsonObject item) {
return !(item.hasKey(ListingJsonConstants.JSONKEY_ITEM_DISABLED)
&& item.getBoolean(
ListingJsonConstants.JSONKEY_ITEM_DISABLED));
}

/**
* Returns whether this item is selected or not.
*
* @param item
* the item, not {@code null}
* @return {@code true} is selected, {@code false} if not
*/
static boolean isSelected(JsonObject item) {
return item.getBoolean(ListingJsonConstants.JSONKEY_ITEM_SELECTED);
}

/**
* Returns the optional icon URL for the given item.
* <p>
* Item icons are not supported by all multiselects.
*
* @param item
* the item
* @return the optional icon URL, or an empty optional if none specified
*/
static Optional<String> getIconUrl(JsonObject item) {
return Optional.ofNullable(
item.getString(ListingJsonConstants.JSONKEY_ITEM_ICON));
}
}

/**
* Returns the multiselect widget for this connector.
* <p>
* This is used because {@link #getWidget()} returns a class
* ({@link Widget}) instead of an interface ({@link IsWidget}), and most
* multiselects extends {@link Composite}.
*
* @return the multiselect widget
*/
public abstract MultiSelectWidget getMultiSelectWidget();

@Override
protected void init() {
super.init();

MultiSelectServerRpc rpcProxy = getRpcProxy(
MultiSelectServerRpc.class);
getMultiSelectWidget().addSelectionChangeListener(
(addedItems, removedItems) -> rpcProxy
.updateSelection(addedItems, removedItems));
}

@Override
public void setDataSource(DataSource<JsonObject> dataSource) {
dataSource.addDataChangeHandler(this::onDataChange);
super.setDataSource(dataSource);
}

/**
* This method handles the parsing of the new JSON data containing the items
* and the selection information.
*
* @param range
* the updated range, never {@code null}
*/
protected void onDataChange(Range range) {
assert range.getStart() == 0
&& range.getEnd() == getDataSource().size() : getClass()
.getSimpleName()
+ " only supports full updates, but got range " + range;
List<JsonObject> items = new ArrayList<>(range.length());
for (int i = 0; i < range.getEnd(); i++) {
items.add(getDataSource().getRow(i));
}
getMultiSelectWidget().setItems(items);
}
}
46 changes: 22 additions & 24 deletions client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java
Expand Up @@ -16,8 +16,6 @@

package com.vaadin.client.ui;

import static com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
Expand All @@ -38,7 +36,7 @@
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.WidgetUtil;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants;
import com.vaadin.shared.ui.ListingJsonConstants;

import elemental.json.JsonObject;

Expand Down Expand Up @@ -77,7 +75,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,

public VCheckBoxGroup() {
optionsContainer = new FlowPanel();
initWidget(this.optionsContainer);
initWidget(optionsContainer);
optionsContainer.setStyleName(CLASSNAME);
optionsToItems = new HashMap<>();
selectionChangeListeners = new ArrayList<>();
Expand All @@ -98,14 +96,14 @@ public void buildOptions(List<JsonObject> items) {
optionsContainer.clear();
for (JsonObject item : items) {
String itemHtml = item
.getString(CheckBoxGroupConstants.JSONKEY_ITEM_VALUE);
.getString(ListingJsonConstants.JSONKEY_ITEM_VALUE);
if (!isHtmlContentAllowed()) {
itemHtml = WidgetUtil.escapeHTML(itemHtml);
}
VCheckBox checkBox = new VCheckBox();

String iconUrl = item
.getString(CheckBoxGroupConstants.JSONKEY_ITEM_ICON);
.getString(ListingJsonConstants.JSONKEY_ITEM_ICON);
if (iconUrl != null && iconUrl.length() != 0) {
Icon icon = client.getIcon(iconUrl);
itemHtml = icon.getElement().getString() + itemHtml;
Expand All @@ -115,10 +113,8 @@ public void buildOptions(List<JsonObject> items) {
checkBox.addClickHandler(this);
checkBox.setHTML(itemHtml);
checkBox.setValue(item
.getBoolean(CheckBoxGroupConstants.JSONKEY_ITEM_SELECTED));
boolean optionEnabled = !item.getBoolean(JSONKEY_ITEM_DISABLED);
boolean enabled = optionEnabled && !isReadonly() && isEnabled();
checkBox.setEnabled(enabled);
.getBoolean(ListingJsonConstants.JSONKEY_ITEM_SELECTED));
setOptionEnabled(checkBox, item);

optionsContainer.add(checkBox);
optionsToItems.put(checkBox, item);
Expand Down Expand Up @@ -152,18 +148,20 @@ public void setTabIndex(int tabIndex) {
}
}

protected void updateEnabledState() {
boolean optionGroupEnabled = isEnabled() && !isReadonly();
// sets options enabled according to the widget's enabled,
// readonly and each options own enabled
for (Map.Entry<VCheckBox, JsonObject> entry : optionsToItems
.entrySet()) {
VCheckBox checkBox = entry.getKey();
JsonObject value = entry.getValue();
Boolean isOptionEnabled = !value
.getBoolean(CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED);
checkBox.setEnabled(optionGroupEnabled && isOptionEnabled);
}
/**
* Updates the checkbox's enabled state according to the widget's enabled,
* read only and the item's enabled.
*
* @param checkBox
* the checkbox to update
* @param item
* the item for the checkbox
*/
protected void setOptionEnabled(VCheckBox checkBox, JsonObject item) {
boolean optionEnabled = !item
.getBoolean(ListingJsonConstants.JSONKEY_ITEM_DISABLED);
boolean enabled = optionEnabled && !isReadonly() && isEnabled();
checkBox.setEnabled(enabled);
}

@Override
Expand Down Expand Up @@ -194,15 +192,15 @@ public boolean isReadonly() {
public void setReadonly(boolean readonly) {
if (this.readonly != readonly) {
this.readonly = readonly;
updateEnabledState();
optionsToItems.forEach(this::setOptionEnabled);
}
}

@Override
public void setEnabled(boolean enabled) {
if (this.enabled != enabled) {
this.enabled = enabled;
updateEnabledState();
optionsToItems.forEach(this::setOptionEnabled);
}
}

Expand Down
34 changes: 17 additions & 17 deletions client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java
Expand Up @@ -16,6 +16,13 @@

package com.vaadin.client.ui;

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 com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
Expand All @@ -33,17 +40,9 @@
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 com.vaadin.shared.ui.ListingJsonConstants;

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;
import elemental.json.JsonObject;

/**
* The client-side widget for the {@code RadioButtonGroup} component.
Expand Down Expand Up @@ -83,7 +82,7 @@ public class VRadioButtonGroup extends Composite implements Field, ClickHandler,
public VRadioButtonGroup() {
groupId = DOM.createUniqueId();
optionsContainer = new FlowPanel();
initWidget(this.optionsContainer);
initWidget(optionsContainer);
optionsContainer.setStyleName(CLASSNAME);
optionsToItems = new HashMap<>();
keyToOptions = new HashMap<>();
Expand All @@ -107,14 +106,14 @@ public void buildOptions(List<JsonObject> items) {
keyToOptions.clear();
for (JsonObject item : items) {
String itemHtml = item
.getString(RadioButtonGroupConstants.JSONKEY_ITEM_VALUE);
.getString(ListingJsonConstants.JSONKEY_ITEM_VALUE);
if (!isHtmlContentAllowed()) {
itemHtml = WidgetUtil.escapeHTML(itemHtml);
}
RadioButton radioButton = new RadioButton(groupId);

String iconUrl = item
.getString(RadioButtonGroupConstants.JSONKEY_ITEM_ICON);
.getString(ListingJsonConstants.JSONKEY_ITEM_ICON);
if (iconUrl != null && iconUrl.length() != 0) {
Icon icon = client.getIcon(iconUrl);
itemHtml = icon.getElement().getString() + itemHtml;
Expand All @@ -124,8 +123,9 @@ public void buildOptions(List<JsonObject> items) {
radioButton.addClickHandler(this);
radioButton.setHTML(itemHtml);
radioButton.setValue(item
.getBoolean(RadioButtonGroupConstants.JSONKEY_ITEM_SELECTED));
boolean optionEnabled = !item.getBoolean(JSONKEY_ITEM_DISABLED);
.getBoolean(ListingJsonConstants.JSONKEY_ITEM_SELECTED));
boolean optionEnabled = !item
.getBoolean(ListingJsonConstants.JSONKEY_ITEM_DISABLED);
boolean enabled = optionEnabled && !isReadonly() && isEnabled();
radioButton.setEnabled(enabled);
String key = item.getString(DataCommunicatorConstants.KEY);
Expand Down Expand Up @@ -175,7 +175,7 @@ protected void updateEnabledState() {
RadioButton radioButton = entry.getKey();
JsonObject value = entry.getValue();
Boolean isOptionEnabled = !value
.getBoolean(RadioButtonGroupConstants.JSONKEY_ITEM_DISABLED);
.getBoolean(ListingJsonConstants.JSONKEY_ITEM_DISABLED);
radioButton.setEnabled(radioButtonEnabled && isOptionEnabled);
}
}
Expand Down Expand Up @@ -229,7 +229,7 @@ public Registration addSelectionChangeHandler(

public void selectItemKey(String selectedItemKey) {
RadioButton radioButton = keyToOptions.get(selectedItemKey);
assert radioButton!=null;
assert radioButton != null;
radioButton.setValue(true);
}
}

0 comments on commit 7e78d52

Please sign in to comment.