* @since
*/
public abstract class AbstractSingleSelect<T> extends
- AbstractListing<T, AbstractSingleSelect<T>.SimpleSingleSelection> {
+ AbstractListing<T, AbstractSingleSelect<T>.AbstractSingleSelection> {
/**
- * A simple single selection model using the {@code AbstractSingleSelect}
- * RPC and state to communicate with the client. Has no client-side
- * counterpart; the listing connector is expected to handle selection.
- * Client-to-server selection is passed via {@link SelectionServerRpc} and
- * server-to-client via {@link AbstractSingleSelectState#selectedItemKey}.
+ * A base class for single selection model implementations. Listens to
+ * {@code SelectionServerRpc} invocations to track selection requests by the
+ * client. Maintaining the selection state and communicating it from the
+ * server to the client is the responsibility of the implementing class.
*/
- public class SimpleSingleSelection implements SelectionModel.Single<T> {
+ public abstract class AbstractSingleSelection implements
+ SelectionModel.Single<T> {
/**
* Creates a new {@code SimpleSingleSelection} instance.
*/
- public SimpleSingleSelection() {
+ public AbstractSingleSelection() {
registerRpc(new SelectionServerRpc() {
@Override
public void select(String key) {
- setSelectedKey(key);
+ setSelectedFromClient(key);
}
@Override
public void deselect(String key) {
- if (Objects.equals(key, getState(false).selectedItemKey)) {
- setSelectedKey(null);
+ if (isKeySelected(key)) {
+ setSelectedFromClient(null);
}
}
});
@Override
public void deselect(T item) {
Objects.requireNonNull(item, "deselected item cannot be null");
- // TODO creates a key if item not in data source
- String key = getDataCommunicator().getKeyMapper().key(item);
- if (Objects.equals(key, getState(false).selectedItemKey)) {
- setSelectedItem(null);
+ if (isSelected(item)) {
+ setSelectedFromServer(null);
}
}
@Override
public void select(T item) {
Objects.requireNonNull(item, "selected item cannot be null");
- setSelectedItem(item);
+ setSelectedFromServer(item);
}
- @Override
- public Optional<T> getSelectedItem() {
- return Optional.ofNullable(getState(false).selectedItemKey).map(
- getDataCommunicator().getKeyMapper()::get);
- }
+ /**
+ * Returns the communication key of the selected item or {@code null} if
+ * no item is selected.
+ *
+ * @return the key of the selected item if any, {@code null} otherwise.
+ */
+ protected abstract String getSelectedKey();
+
+ /**
+ * Sets the selected item based on the given communication key. If the
+ * key is {@code null}, clears the current selection if any.
+ *
+ * @param key
+ * the key of the selected item or {@code null} to clear
+ * selection
+ */
+ protected abstract void doSetSelectedKey(String key);
- private void setSelectedKey(String key) {
+ /**
+ * Sets the selection based on a client request. Does nothing if the
+ * select component is {@linkplain Component#isReadOnly()} or if the
+ * selection would not change. Otherwise updates the selection and fires
+ * a selection change event with {@code isUserOriginated == true}.
+ *
+ * @param key
+ * the key of the item to select or {@code null} to clear
+ * selection
+ */
+ protected void setSelectedFromClient(String key) {
if (isReadOnly()) {
return;
}
- if (Objects.equals(key, getState(false).selectedItemKey)) {
+ if (isKeySelected(key)) {
return;
}
- getState().selectedItemKey = key;
+ doSetSelectedKey(key);
fireEvent(new SingleSelectionChange<>(AbstractSingleSelect.this,
getSelectedItem().orElse(null), true));
}
- private void setSelectedItem(T item) {
+ /**
+ * Sets the selection based on server API call. Does nothing if the
+ * selection would not change; otherwise updates the selection and fires
+ * a selection change event with {@code isUserOriginated == false}.
+ *
+ * @param item
+ * the item to select or {@code null} to clear selection
+ */
+ protected void setSelectedFromServer(T item) {
// TODO creates a key if item not in data source
- String key = Optional.ofNullable(item).map(getDataCommunicator()
- .getKeyMapper()::key).orElse(null);
+ String key = itemToKey(item);
- if (Objects.equals(key, getState(false).selectedItemKey)) {
+ if (isKeySelected(key) || isSelected(item)) {
return;
}
- getState().selectedItemKey = key;
+ doSetSelectedKey(key);
fireEvent(new SingleSelectionChange<>(AbstractSingleSelect.this,
item, false));
}
+
+ /**
+ * Returns whether the given key maps to the currently selected item.
+ *
+ * @param key
+ * the key to test or {@code null} to test whether nothing is
+ * selected
+ * @return {@code true} if the key equals the key of the currently
+ * selected item (or {@code null} if no selection),
+ * {@code false} otherwise.
+ */
+ protected boolean isKeySelected(String key) {
+ return Objects.equals(key, getSelectedKey());
+ }
+
+ /**
+ * Returns the communication key assigned to the given item.
+ *
+ * @param item
+ * the item whose key to return
+ * @return the assigned key
+ */
+ protected String itemToKey(T item) {
+ if (item == null) {
+ return null;
+ } else {
+ // TODO creates a key if item not in data source
+ return getDataCommunicator().getKeyMapper().key(item);
+ }
+ }
+
+ /**
+ * Returns the item that the given key is assigned to, or {@code null}
+ * if there is no such item.
+ *
+ * @param key
+ * the key whose item to return
+ * @return the associated item if any, {@code null} otherwise.
+ */
+ protected T keyToItem(String key) {
+ return getDataCommunicator().getKeyMapper().get(key);
+ }
+ }
+
+ /**
+ * A simple single selection model using the {@code AbstractSingleSelect}
+ * RPC and state to communicate with the client. Has no client-side
+ * counterpart; the listing connector is expected to handle selection.
+ * Client-to-server selection is passed via {@link SelectionServerRpc} and
+ * server-to-client via {@link AbstractSingleSelectState#selectedItemKey}.
+ */
+ protected class SimpleSingleSelection extends AbstractSingleSelection {
+
+ /**
+ * Creates a new {@code SimpleSingleSelection}.
+ */
+ public SimpleSingleSelection() {
+ }
+
+ @Override
+ public Optional<T> getSelectedItem() {
+ return Optional.ofNullable(keyToItem(getSelectedKey()));
+ }
+
+ @Override
+ protected String getSelectedKey() {
+ return getState(false).selectedItemKey;
+ }
+
+ @Override
+ protected void doSetSelectedKey(String key) {
+ getState().selectedItemKey = key;
+ }
}
@Deprecated
--- /dev/null
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.server.data.datasource.bov.Person;
+import com.vaadin.shared.data.DataCommunicatorClientRpc;
+import com.vaadin.ui.AbstractSingleSelect.AbstractSingleSelection;
+
+/**
+ * Test for {@link AbstractSingleSelect} and {@link AbstractSingleSelection}
+ *
+ * @author Vaadin Ltd
+ */
+public class AbstractSingleSelectTest {
+
+ private PersonListing.AbstractSingleSelection selectionModel;
+ private List<Person> selectionChanges;
+
+ private static class PersonListing extends
+ AbstractSingleSelect<Person> {
+ public PersonListing() {
+ setSelectionModel(new SimpleSingleSelection());
+ }
+ }
+
+ @Before
+ public void initListing() {
+ listing = new PersonListing();
+ listing.setItems(PERSON_A, PERSON_B, PERSON_C);
+ selectionModel = listing.getSelectionModel();
+ selectionChanges = new ArrayList<>();
+ listing.addSelectionListener(e -> selectionChanges.add(e.getValue()));
+ }
+
+ public static final Person PERSON_C = new Person("c", 3);
+ public static final Person PERSON_B = new Person("b", 2);
+ public static final Person PERSON_A = new Person("a", 1);
+ public static final String RPC_INTERFACE = DataCommunicatorClientRpc.class
+ .getName();
+ private PersonListing listing;
+
+ @Test
+ public void select() {
+
+ selectionModel.select(PERSON_B);
+
+ assertTrue(selectionModel.getSelectedItem().isPresent());
+
+ assertEquals(PERSON_B, selectionModel.getSelectedItem().orElse(null));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Collections.singleton(PERSON_B), selectionModel
+ .getSelectedItems());
+
+ assertEquals(Arrays.asList(PERSON_B), selectionChanges);
+ }
+
+ @Test
+ public void selectDeselect() {
+
+ selectionModel.select(PERSON_B);
+ selectionModel.deselect(PERSON_B);
+
+ assertFalse(selectionModel.getSelectedItem().isPresent());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertTrue(selectionModel.getSelectedItems().isEmpty());
+
+ assertEquals(Arrays.asList(PERSON_B, null), selectionChanges);
+ }
+
+ @Test
+ public void reselect() {
+
+ selectionModel.select(PERSON_B);
+ selectionModel.select(PERSON_C);
+
+ assertEquals(PERSON_C, selectionModel.getSelectedItem().orElse(null));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Collections.singleton(PERSON_C), selectionModel
+ .getSelectedItems());
+
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C), selectionChanges);
+ }
+
+ @Test
+ public void deselectNoOp() {
+
+ selectionModel.select(PERSON_C);
+ selectionModel.deselect(PERSON_B);
+
+ assertEquals(PERSON_C, selectionModel.getSelectedItem().orElse(null));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Collections.singleton(PERSON_C), selectionModel
+ .getSelectedItems());
+
+ assertEquals(Arrays.asList(PERSON_C), selectionChanges);
+ }
+
+ @Test
+ public void selectTwice() {
+
+ selectionModel.select(PERSON_C);
+ selectionModel.select(PERSON_C);
+
+ assertEquals(PERSON_C, selectionModel.getSelectedItem().orElse(null));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Collections.singleton(PERSON_C), selectionModel
+ .getSelectedItems());
+
+ assertEquals(Arrays.asList(PERSON_C), selectionChanges);
+ }
+
+ @Test
+ public void deselectTwice() {
+
+ selectionModel.select(PERSON_C);
+ selectionModel.deselect(PERSON_C);
+ selectionModel.deselect(PERSON_C);
+
+ assertFalse(selectionModel.getSelectedItem().isPresent());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertTrue(selectionModel.getSelectedItems().isEmpty());
+
+ assertEquals(Arrays.asList(PERSON_C, null), selectionChanges);
+ }
+
+}