/* * 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.components.grid; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import com.vaadin.event.selection.SingleSelectionEvent; import com.vaadin.event.selection.SingleSelectionListener; import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.ui.grid.SingleSelectionModelState; import com.vaadin.ui.SingleSelect; /** * Single selection model for grid. * * @author Vaadin Ltd. * @since 8.0 * * @param <T> * the type of the selected item in grid. */ public class SingleSelectionModelImpl<T> extends AbstractSelectionModel<T> implements SingleSelectionModel<T> { private T selectedItem = null; @Override protected void init() { registerRpc(new SelectionServerRpc() { @Override public void select(String key) { setSelectedFromClient(key); } @Override public void deselect(String key) { if (isKeySelected(key)) { setSelectedFromClient(null); } } }); } @Override protected SingleSelectionModelState getState() { return (SingleSelectionModelState) super.getState(); } @Override protected SingleSelectionModelState getState(boolean markAsDirty) { return (SingleSelectionModelState) super.getState(markAsDirty); } @Override public Registration addSingleSelectionListener( SingleSelectionListener<T> listener) { return addListener(SingleSelectionEvent.class, listener, SingleSelectionListener.SELECTION_CHANGE_METHOD); } @Override public Optional<T> getSelectedItem() { return Optional.ofNullable(selectedItem); } @Override public void deselect(T item) { Objects.requireNonNull(item, "deselected item cannot be null"); if (isSelected(item)) { setSelectedFromServer(null); } } @Override public void select(T item) { Objects.requireNonNull(item, "selected item cannot be null"); setSelectedFromServer(item); } /** * 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 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 String getSelectedKey() { return itemToKey(selectedItem); } /** * 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 void doSetSelectedKey(String key) { if (getParent() == null) { throw new IllegalStateException( "Trying to update selection for grid selection model that has been detached from the grid."); } if (selectedItem != null) { getGrid().getDataCommunicator().refresh(selectedItem); } selectedItem = getData(key); if (selectedItem != null) { getGrid().getDataCommunicator().refresh(selectedItem); } } /** * Sets the selection based on a client request. Does nothing if the select * component is {@linkplain SingleSelect#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 (!isUserSelectionAllowed()) { throw new IllegalStateException("Client tried to update selection" + " although user selection is disallowed"); } if (isKeySelected(key)) { return; } T oldSelection = this.getSelectedItem().orElse(null); doSetSelectedKey(key); fireEvent(new SingleSelectionEvent<>(getGrid(), asSingleSelect(), oldSelection, true)); } /** * 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 provider String key = itemToKey(item); if (isSelected(item) || isKeySelected(key)) { return; } T oldSelection = this.getSelectedItem() .orElse(asSingleSelect().getEmptyValue()); doSetSelectedKey(key); fireEvent(new SingleSelectionEvent<>(getGrid(), asSingleSelect(), oldSelection, false)); } /** * 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 provider return getGrid().getDataCommunicator().getKeyMapper().key(item); } } @Override public Set<T> getSelectedItems() { if (selectedItem != null) { return new HashSet<>(Arrays.asList(selectedItem)); } else { return Collections.emptySet(); } } @Override public void setDeselectAllowed(boolean deselectAllowed) { getState().deselectAllowed = deselectAllowed; } @Override public boolean isDeselectAllowed() { return getState().deselectAllowed; } /** * Gets a wrapper for using this grid as a single select in a binder. * * @return a single select wrapper for grid */ @Override public SingleSelect<T> asSingleSelect() { return new SingleSelect<T>() { @Override public void setValue(T value) { SingleSelectionModelImpl.this.setSelectedFromServer(value); } @Override public T getValue() { return SingleSelectionModelImpl.this.getSelectedItem() .orElse(null); } @Override public Registration addValueChangeListener( com.vaadin.data.HasValue.ValueChangeListener<T> listener) { return SingleSelectionModelImpl.this.addSingleSelectionListener( (SingleSelectionListener<T>) event -> listener .valueChange(event)); } @Override public void setRequiredIndicatorVisible( boolean requiredIndicatorVisible) { // TODO support required indicator when grid is used in binder ? throw new UnsupportedOperationException( "Required indicator is not supported for Grid."); } @Override public boolean isRequiredIndicatorVisible() { throw new UnsupportedOperationException( "Required indicator is not supported for Grid."); } @Override public void setReadOnly(boolean readOnly) { setUserSelectionAllowed(!readOnly); } @Override public boolean isReadOnly() { return !isUserSelectionAllowed(); } }; } @Override public void refreshData(T item) { if (isSelected(item)) { selectedItem = item; } } @Override public boolean isSelected(T item) { return item != null && selectedItem != null && getGrid().getDataProvider().getId(selectedItem) .equals(getGrid().getDataProvider().getId(item)); } }