diff options
author | Teemu Suo-Anttila <teemusa@vaadin.com> | 2016-02-03 14:19:25 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-03-05 12:02:08 +0000 |
commit | acab3d3c0c8eda303a8aa331ba5b17d38164542a (patch) | |
tree | c9690995d71c6efe71f58f4b37ed01e07f9c0582 | |
parent | eca0b72f697b3e79690c2dc02014ad7953edd6cd (diff) | |
download | vaadin-framework-acab3d3c0c8eda303a8aa331ba5b17d38164542a.tar.gz vaadin-framework-acab3d3c0c8eda303a8aa331ba5b17d38164542a.zip |
Add simple ListBox component to use with the DataProvider
This patch changes the DummyDataProvider test to use the ListBox storing
runnables instead of Buttons with actions.
Change-Id: Ie3831ccbd397eb2c145cf2c036fa5e512dcfa230
6 files changed, 415 insertions, 22 deletions
diff --git a/client/src/com/vaadin/client/connectors/proto/ListBoxConnector.java b/client/src/com/vaadin/client/connectors/proto/ListBoxConnector.java new file mode 100644 index 0000000000..d5857b2571 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/proto/ListBoxConnector.java @@ -0,0 +1,141 @@ +/* + * 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.client.connectors.proto; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.user.client.ui.ListBox; +import com.vaadin.client.data.DataChangeHandler; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.HasDataSource; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.proto.listbox.ListBoxConstants; +import com.vaadin.shared.ui.proto.listbox.ListBoxSelectRpc; + +import elemental.json.JsonObject; + +@Connect(com.vaadin.ui.proto.ListBox.class) +public class ListBoxConnector extends AbstractComponentConnector implements + HasDataSource { + + private DataSource<JsonObject> dataSource; + private ListBoxSelectRpc rpc; + + @Override + public ListBox getWidget() { + return (ListBox) super.getWidget(); + } + + @Override + protected void init() { + super.init(); + + rpc = getRpcProxy(ListBoxSelectRpc.class); + getWidget().addChangeHandler(new ChangeHandler() { + + @Override + public void onChange(ChangeEvent event) { + // Send selection to server + rpc.select(getWidget().getSelectedValue()); + } + }); + + // TODO: Add some way to handle empty selection naming. + getWidget().addItem(" ", ""); + getWidget().setSelectedIndex(0); + } + + @Override + public void setDataSource(DataSource<JsonObject> rpcDataSource) { + // Clean up old data source + if (dataSource != null) { + dataSource.setDataChangeHandler(null); + } + + dataSource = rpcDataSource; + dataSource.setDataChangeHandler(new DataChangeHandler() { + + @Override + public void resetDataAndSize(int estimatedNewDataSize) { + recreateOptions(); + } + + @Override + public void dataUpdated(int firstRowIndex, int numberOfRows) { + recreateOptions(); + } + + @Override + public void dataRemoved(int firstRowIndex, int numberOfRows) { + boolean resetSelection = false; + + // Offset by 1, due to empty item. + int removedIndex = firstRowIndex + 1; + + for (int i = 0; i < numberOfRows + && removedIndex < getWidget().getItemCount(); ++i) { + if (!resetSelection + && getWidget().getSelectedIndex() == removedIndex) { + resetSelection = true; + } + getWidget().removeItem(removedIndex); + } + + if (resetSelection) { + getWidget().setSelectedIndex(0); + // No event from setSelectedIndex, call manually. + rpc.select(""); + } + } + + @Override + public void dataAvailable(int firstRowIndex, int numberOfRows) { + recreateOptions(); + } + + @Override + public void dataAdded(int firstRowIndex, int numberOfRows) { + // Add new ones to end of list, no matter the actual place + for (int i = 0; i < numberOfRows; ++i) { + addItem(firstRowIndex + i); + } + } + }); + } + + private void recreateOptions() { + // Remove all non-empty items. + while (getWidget().getItemCount() > 1) { + getWidget().removeItem(1); + } + + for (int i = 0; i < dataSource.size(); ++i) { + addItem(i); + } + getWidget().setSelectedIndex(0); + } + + protected void addItem(int index) { + JsonObject item = dataSource.getRow(index); + if (item != null) { + // We wrote the name provider output to "n" + // Key is always stored at "k" + getWidget().addItem(item.getString(ListBoxConstants.NAME_KEY), + item.getString("k")); + } + } +}
\ No newline at end of file diff --git a/server/src/com/vaadin/ui/proto/ListBox.java b/server/src/com/vaadin/ui/proto/ListBox.java new file mode 100644 index 0000000000..bc952c1617 --- /dev/null +++ b/server/src/com/vaadin/ui/proto/ListBox.java @@ -0,0 +1,145 @@ +/* + * 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.proto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.vaadin.server.communication.data.typed.CollectionDataSource; +import com.vaadin.server.communication.data.typed.DataProvider; +import com.vaadin.server.communication.data.typed.DataSource; +import com.vaadin.server.communication.data.typed.TypedDataGenerator; +import com.vaadin.shared.ui.proto.listbox.ListBoxConstants; +import com.vaadin.shared.ui.proto.listbox.ListBoxSelectRpc; +import com.vaadin.ui.AbstractComponent; + +import elemental.json.JsonObject; + +/** + * ListBox is a simple select component typed to a data type. + * + * @since + */ +public class ListBox<T> extends AbstractComponent { + + private static class DefaultNameProvider<T> implements NameProvider<T> { + @Override + public String getName(T value) { + return value.toString(); + } + } + + public interface NameProvider<T> extends Serializable { + String getName(T value); + } + + public interface ValueChange<T> extends Serializable { + void valueChange(T value); + } + + private T selected; + private DataProvider<T> dataProvider; + private Set<ValueChange<T>> listeners = new LinkedHashSet<ValueChange<T>>(); + + public ListBox(Collection<T> data) { + this(new CollectionDataSource<T>(data), new DefaultNameProvider<T>()); + } + + public ListBox(DataSource<T> data) { + this(data, new DefaultNameProvider<T>()); + } + + public ListBox(Collection<T> data, NameProvider<T> nameProvider) { + this(new CollectionDataSource<T>(data), nameProvider); + } + + public ListBox(DataSource<T> data, NameProvider<T> nameProvider) { + setDataSource(data, nameProvider); + registerRpc(new ListBoxSelectRpc() { + + @Override + public void select(String key) { + if (key != null && !key.isEmpty()) { + // Key mapper gives the object represented by the given + // key communicated to the client-side + selected = dataProvider.getKeyMapper().get(key); + fireSelectionChange(selected); + } else { + selected = null; + } + } + }); + } + + public ListBox() { + this(new CollectionDataSource<T>(new ArrayList<T>()), + new DefaultNameProvider<T>()); + + } + + public void setDataSource(Collection<T> data) { + setDataSource(data, new DefaultNameProvider<T>()); + } + + public void setDataSource(DataSource<T> data) { + setDataSource(data, new DefaultNameProvider<T>()); + } + + public void setDataSource(Collection<T> data, NameProvider<T> nameProvider) { + setDataSource(new CollectionDataSource<T>(data), nameProvider); + } + + public void setDataSource(DataSource<T> data, + final NameProvider<T> nameProvider) { + dataProvider = DataProvider.create(data, this); + dataProvider.addDataGenerator(new TypedDataGenerator<T>() { + + @Override + public void generateData(T bean, JsonObject rowData) { + rowData.put(ListBoxConstants.NAME_KEY, + nameProvider.getName(bean)); + } + + @Override + public void destroyData(T bean) { + // No data needs to be destroyed. + } + }); + } + + public void addValueChangeListener(ValueChange<T> listener) { + listeners.add(listener); + } + + public void removeValueChangeListener(ValueChange<T> listener) { + listeners.remove(listener); + } + + public T getSelected() { + return selected; + } + + protected void fireSelectionChange(T selected) { + List<ValueChange<T>> set = new ArrayList<ValueChange<T>>(listeners); + for (ValueChange<T> listener : set) { + listener.valueChange(selected); + } + } +} diff --git a/shared/src/com/vaadin/shared/ui/proto/listbox/ListBoxConstants.java b/shared/src/com/vaadin/shared/ui/proto/listbox/ListBoxConstants.java new file mode 100644 index 0000000000..3f6ef75683 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/proto/listbox/ListBoxConstants.java @@ -0,0 +1,27 @@ +/* + * 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.shared.ui.proto.listbox; + +import java.io.Serializable; + +/** + * Constants used in ListBox communication. + * + * @since + */ +public final class ListBoxConstants implements Serializable { + public static final String NAME_KEY = "n"; +} diff --git a/shared/src/com/vaadin/shared/ui/proto/listbox/ListBoxSelectRpc.java b/shared/src/com/vaadin/shared/ui/proto/listbox/ListBoxSelectRpc.java new file mode 100644 index 0000000000..fa37463cea --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/proto/listbox/ListBoxSelectRpc.java @@ -0,0 +1,29 @@ +/* + * 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.shared.ui.proto.listbox; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * Simple client to server rpc to inform ListBox component about selection. + * + * @since + */ +public interface ListBoxSelectRpc extends ServerRpc { + + void select(String key); + +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderTest.java b/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderTest.java index 3821428ab2..9396dc05f0 100644 --- a/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderTest.java +++ b/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderTest.java @@ -29,9 +29,12 @@ import java.util.Random; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.Select; import com.vaadin.shared.data.DataProviderConstants; +import com.vaadin.testbench.elements.AbstractComponentElement; import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elementsbase.ServerClass; import com.vaadin.tests.fieldgroup.ComplexPerson; import com.vaadin.tests.tb3.SingleBrowserTest; @@ -40,6 +43,14 @@ import elemental.json.JsonObject; public class DummyDataProviderTest extends SingleBrowserTest { + @ServerClass("com.vaadin.ui.proto.ListBox") + public static class ListBoxElement extends AbstractComponentElement { + + public void selectByText(String text) { + new Select(this).selectByVisibleText(text); + } + } + // Each test uses a set of person objects (generated json) that is supposed // to match the data sent to the client-side. private List<JsonObject> personObjects = new ArrayList<JsonObject>(); @@ -113,10 +124,12 @@ public class DummyDataProviderTest extends SingleBrowserTest { openTestURL(); - $(ButtonElement.class).id("sort").click(); + $(ListBoxElement.class).first().selectByText("sort"); + ButtonElement button = $(ButtonElement.class).first(); + button.click(); // Second sort would show if any keys got destroyed/recreated. - $(ButtonElement.class).id("sort").click(); + button.click(); int size = DummyDataProviderUI.PERSON_COUNT + 1; List<WebElement> labels = findElements(By.className("v-label")); @@ -143,13 +156,16 @@ public class DummyDataProviderTest extends SingleBrowserTest { openTestURL(); - $(ButtonElement.class).id("sort").click(); + $(ListBoxElement.class).first().selectByText("sort"); + ButtonElement button = $(ButtonElement.class).first(); + button.click(); String text = findElements(By.className("v-label")).get(3).getText(); String json = personObjects.get(2).toJson(); assertEquals("Data not sorted", json, text); - $(ButtonElement.class).id("remove").click(); + $(ListBoxElement.class).first().selectByText("remove"); + button.click(); text = findElements(By.className("v-label")).get(3).getText(); json = personObjects.get(3).toJson(); @@ -169,7 +185,9 @@ public class DummyDataProviderTest extends SingleBrowserTest { String text = findElements(By.className("v-label")).get(1).getText(); assertEquals("Initial data did not match", json, text); - $(ButtonElement.class).id("edit").click(); + $(ListBoxElement.class).first().selectByText("edit"); + ButtonElement button = $(ButtonElement.class).first(); + button.click(); persons.get(0).setFirstName("Foo"); createPersonObjects(); @@ -182,7 +200,8 @@ public class DummyDataProviderTest extends SingleBrowserTest { text = findElements(By.className("v-label")).get(1).getText(); assertEquals("Modified data did not match", json, text); - $(ButtonElement.class).id("edit").click(); + $(ListBoxElement.class).first().selectByText("edit"); + button.click(); text = findElements(By.className("v-label")).get(1).getText(); assertEquals("Running edit again shouldn't change anything", json, text); diff --git a/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java b/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java index 0cd45f1994..984035a5d1 100644 --- a/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java +++ b/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java @@ -15,10 +15,12 @@ */ package com.vaadin.tests.dataprovider; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Random; @@ -37,12 +39,27 @@ import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.proto.ListBox; +import com.vaadin.ui.proto.ListBox.NameProvider; import elemental.json.JsonObject; @Widgetset(TestingWidgetSet.NAME) public class DummyDataProviderUI extends AbstractTestUI { + abstract static class MyRunnable implements Runnable, Serializable { + + private final String name; + + public MyRunnable(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + public static class DummyDataComponent extends AbstractComponent { private SimpleDataProvider<ComplexPerson> dataProvider; @@ -99,50 +116,65 @@ public class DummyDataProviderUI extends AbstractTestUI { private Random r = new Random(RANDOM_SEED); private MyDataSource dataSource; private DummyDataComponent dummy; + private ListBox<MyRunnable> listBox; + private List<ComplexPerson> persons; @Override protected void setup(VaadinRequest request) { - dataSource = new MyDataSource(createPersons(PERSON_COUNT, r)); + + persons = createPersons(PERSON_COUNT, r); + dataSource = new MyDataSource(persons); dummy = new DummyDataComponent(dataSource); - Button remove = new Button("Remove third", new ClickListener() { + Collection<MyRunnable> actions = new LinkedHashSet<MyRunnable>(); + actions.add(new MyRunnable("remove") { @Override - public void buttonClick(ClickEvent event) { + public void run() { dataSource.remove(dataSource.getData().get(2)); } }); - Button add = new Button("Add new", new ClickListener() { + actions.add(new MyRunnable("add") { @Override - public void buttonClick(ClickEvent event) { + public void run() { dataSource.save(ComplexPerson.create(r)); } }); - Button sort = new Button("Sort content", new ClickListener() { + actions.add(new MyRunnable("sort") { @Override - public void buttonClick(ClickEvent event) { + public void run() { dataSource.sort(nameComparator); } }); - Button edit = new Button("Edit first", new ClickListener() { + actions.add(new MyRunnable("edit") { @Override - public void buttonClick(ClickEvent event) { - ComplexPerson p = dataSource.iterator().next(); + public void run() { + ComplexPerson p = persons.get(0); p.setFirstName("Foo"); dataSource.save(p); } }); - // Button Ids - remove.setId("remove"); - add.setId("add"); - sort.setId("sort"); - edit.setId("edit"); + listBox = new ListBox<MyRunnable>(actions, + new NameProvider<MyRunnable>() { + + @Override + public String getName(MyRunnable value) { + return value.getName(); + } + }); + + Button execute = new Button("Execute", new ClickListener() { - addComponent(new HorizontalLayout(add, remove, sort, edit)); + @Override + public void buttonClick(ClickEvent event) { + listBox.getSelected().run(); + } + }); + addComponent(new HorizontalLayout(listBox, execute)); addComponent(dummy); } |