You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractSingleSelect.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.util.Collection;
  18. import java.util.HashSet;
  19. import java.util.List;
  20. import java.util.Objects;
  21. import java.util.Optional;
  22. import java.util.Set;
  23. import java.util.stream.Collectors;
  24. import org.jsoup.nodes.Element;
  25. import com.vaadin.data.HasValue;
  26. import com.vaadin.data.SelectionModel.Single;
  27. import com.vaadin.data.provider.DataCommunicator;
  28. import com.vaadin.data.provider.DataGenerator;
  29. import com.vaadin.event.selection.SingleSelectionEvent;
  30. import com.vaadin.event.selection.SingleSelectionListener;
  31. import com.vaadin.shared.Registration;
  32. import com.vaadin.shared.data.selection.SelectionServerRpc;
  33. import com.vaadin.shared.ui.AbstractSingleSelectState;
  34. import com.vaadin.ui.declarative.DesignContext;
  35. import com.vaadin.ui.declarative.DesignException;
  36. import elemental.json.Json;
  37. import elemental.json.JsonObject;
  38. /**
  39. * An abstract base class for listing components that only support single
  40. * selection and no lazy loading of data items.
  41. *
  42. * @author Vaadin Ltd.
  43. *
  44. * @param <T>
  45. * the item date type
  46. *
  47. * @see com.vaadin.data.SelectionModel.Single
  48. *
  49. * @since 8.0
  50. */
  51. public abstract class AbstractSingleSelect<T> extends AbstractListing<T>
  52. implements SingleSelect<T> {
  53. private T selectedItem = null;
  54. /**
  55. * Creates a new {@code AbstractListing} with a default data communicator.
  56. * <p>
  57. */
  58. protected AbstractSingleSelect() {
  59. init();
  60. }
  61. /**
  62. * Creates a new {@code AbstractSingleSelect} with the given custom data
  63. * communicator.
  64. * <p>
  65. * <strong>Note:</strong> This method is for creating an
  66. * {@code AbstractSingleSelect} with a custom communicator. In the common
  67. * case {@link AbstractSingleSelect#AbstractSingleSelect()} should be used.
  68. * <p>
  69. *
  70. * @param dataCommunicator
  71. * the data communicator to use, not null
  72. */
  73. protected AbstractSingleSelect(DataCommunicator<T> dataCommunicator) {
  74. super(dataCommunicator);
  75. init();
  76. }
  77. /**
  78. * Adds a selection listener to this select. The listener is called when the
  79. * selection is changed either by the user or programmatically.
  80. *
  81. * @param listener
  82. * the selection listener, not null
  83. * @return a registration for the listener
  84. */
  85. public Registration addSelectionListener(
  86. SingleSelectionListener<T> listener) {
  87. return addListener(SingleSelectionEvent.class, listener,
  88. SingleSelectionListener.SELECTION_CHANGE_METHOD);
  89. }
  90. /**
  91. * Returns the currently selected item, or an empty optional if no item is
  92. * selected.
  93. *
  94. * @return an optional of the selected item if any, an empty optional
  95. * otherwise
  96. */
  97. public Optional<T> getSelectedItem() {
  98. return Optional.ofNullable(selectedItem);
  99. }
  100. /**
  101. * Sets the current selection to the given item or clears selection if given
  102. * {@code null}.
  103. *
  104. * @param item
  105. * the item to select or {@code null} to clear selection
  106. */
  107. public void setSelectedItem(T item) {
  108. setSelectedItem(item, false);
  109. }
  110. /**
  111. * Returns the current value of this object which is the currently selected
  112. * item.
  113. * <p>
  114. * The call is delegated to {@link #getSelectedItem()}
  115. *
  116. * @return the current selection, may be {@code null}
  117. *
  118. * @see #getSelectedItem()
  119. * @see Single#getSelectedItem
  120. */
  121. @Override
  122. public T getValue() {
  123. return getSelectedItem().orElse(null);
  124. }
  125. /**
  126. * Sets the value of this object which is an item to select. If the new
  127. * value is not equal to {@code getValue()}, fires a value change event. If
  128. * value is {@code null} then it deselects currently selected item.
  129. * <p>
  130. * The call is delegated to {@link #setSelectedItem(Object)}.
  131. *
  132. * @see #setSelectedItem(Object)
  133. * @see Single#setSelectedItem(Object)
  134. *
  135. * @param value
  136. * the item to select or {@code null} to clear selection
  137. */
  138. @Override
  139. public void setValue(T value) {
  140. setSelectedItem(value);
  141. }
  142. @Override
  143. public Registration addValueChangeListener(
  144. HasValue.ValueChangeListener<T> listener) {
  145. return addSelectionListener(
  146. event -> listener.valueChange(new ValueChangeEvent<>(this,
  147. event.getOldValue(), event.isUserOriginated())));
  148. }
  149. @Override
  150. protected AbstractSingleSelectState getState() {
  151. return (AbstractSingleSelectState) super.getState();
  152. }
  153. @Override
  154. protected AbstractSingleSelectState getState(boolean markAsDirty) {
  155. return (AbstractSingleSelectState) super.getState(markAsDirty);
  156. }
  157. @Override
  158. public void setRequiredIndicatorVisible(boolean visible) {
  159. super.setRequiredIndicatorVisible(visible);
  160. }
  161. @Override
  162. public boolean isRequiredIndicatorVisible() {
  163. return super.isRequiredIndicatorVisible();
  164. }
  165. @Override
  166. public void setReadOnly(boolean readOnly) {
  167. super.setReadOnly(readOnly);
  168. }
  169. @Override
  170. public boolean isReadOnly() {
  171. return super.isReadOnly();
  172. }
  173. /**
  174. * Returns the item that the given key is assigned to, or {@code null} if
  175. * there is no such item.
  176. *
  177. * @param key
  178. * the key whose item to return
  179. * @return the associated item if any, {@code null} otherwise.
  180. */
  181. protected T keyToItem(String key) {
  182. return getDataCommunicator().getKeyMapper().get(key);
  183. }
  184. /**
  185. * Returns whether the given item is currently selected.
  186. *
  187. * @param item
  188. * the item to check, not null
  189. * @return {@code true} if the item is selected, {@code false} otherwise
  190. */
  191. public boolean isSelected(T item) {
  192. if (Objects.equals(selectedItem, item)) {
  193. return true;
  194. }
  195. if (item == null || selectedItem == null) {
  196. return false;
  197. }
  198. return Objects.equals(getDataProvider().getId(selectedItem),
  199. getDataProvider().getId(item));
  200. }
  201. @Override
  202. protected Element writeItem(Element design, T item, DesignContext context) {
  203. Element element = super.writeItem(design, item, context);
  204. if (isSelected(item)) {
  205. element.attr("selected", true);
  206. }
  207. return element;
  208. }
  209. @Override
  210. protected void readItems(Element design, DesignContext context) {
  211. Set<T> selected = new HashSet<>();
  212. List<T> items = design.children().stream()
  213. .map(child -> readItem(child, selected, context))
  214. .collect(Collectors.toList());
  215. if (!items.isEmpty()) {
  216. setItems(items);
  217. }
  218. selected.forEach(this::setValue);
  219. }
  220. /**
  221. * Reads an Item from a design and inserts it into the data source.
  222. * Hierarchical select components should override this method to recursively
  223. * recursively read any child items as well.
  224. *
  225. * @param child
  226. * a child element representing the item
  227. * @param selected
  228. * A set accumulating selected items. If the item that is read is
  229. * marked as selected, its item id should be added to this set.
  230. * @param context
  231. * the DesignContext instance used in parsing
  232. * @return the item id of the new item
  233. *
  234. * @throws DesignException
  235. * if the tag name of the {@code child} element is not
  236. * {@code option}.
  237. */
  238. protected T readItem(Element child, Set<T> selected,
  239. DesignContext context) {
  240. T item = readItem(child, context);
  241. if (child.hasAttr("selected")) {
  242. selected.add(item);
  243. }
  244. return item;
  245. }
  246. @Override
  247. protected Collection<String> getCustomAttributes() {
  248. Collection<String> attributes = super.getCustomAttributes();
  249. // "value" is not an attribute for the component. "selected" attribute
  250. // is used in "option"'s tag to mark selection which implies value for
  251. // single select component
  252. attributes.add("value");
  253. return attributes;
  254. }
  255. private void init() {
  256. registerRpc(new SelectionServerRpc() {
  257. @Override
  258. public void select(String key) {
  259. setSelectedItem(keyToItem(key), true);
  260. }
  261. @Override
  262. public void deselect(String key) {
  263. T item = keyToItem(key);
  264. if (isSelected(item)) {
  265. setSelectedItem(null, true);
  266. }
  267. }
  268. });
  269. addDataGenerator(new DataGenerator<T>() {
  270. @Override
  271. public void generateData(T item, JsonObject jsonObject) {
  272. if (isSelected(item)) {
  273. // Deferred update of state.
  274. updateSelectedItemState(item);
  275. }
  276. }
  277. @Override
  278. public void refreshData(T item) {
  279. if (isSelected(item)) {
  280. selectedItem = item;
  281. // Invalidate old data
  282. updateSelectedItemState(null);
  283. }
  284. }
  285. });
  286. }
  287. /**
  288. * This method updates the internal selection state of the server-side of
  289. * {@code AbstractSingleSelect}.
  290. *
  291. * @param value
  292. * the value that should be selected
  293. * @param userOriginated
  294. * {@code true} if selection was done by user, {@code false} if
  295. * not
  296. *
  297. * @since 8.5
  298. */
  299. protected void setSelectedItem(T value, boolean userOriginated) {
  300. if (isSelected(value)) {
  301. return;
  302. }
  303. // Update selection
  304. T oldValue = selectedItem;
  305. selectedItem = value;
  306. // Re-generate selected item data
  307. if (oldValue != null) {
  308. getDataCommunicator().refresh(oldValue);
  309. }
  310. if (value != null) {
  311. getDataCommunicator().refresh(value);
  312. }
  313. // Deselection can be handled immediately
  314. updateSelectedItemState(value);
  315. // Update diffstate to make sure null can be selected later.
  316. updateDiffstate("selectedItemKey", Json.createObject());
  317. fireEvent(new SingleSelectionEvent<>(AbstractSingleSelect.this,
  318. oldValue, userOriginated));
  319. }
  320. /**
  321. * This method updates the shared selection state of the
  322. * {@code AbstractSingleSelect}.
  323. *
  324. * @param value
  325. * the value that is selected; may be {@code null}
  326. *
  327. * @since 8.5
  328. */
  329. protected void updateSelectedItemState(T value) {
  330. // FIXME: If selecting a value that does not exist, this will leave and
  331. // extra object in the key mapper that will not be dropped any time.
  332. getState().selectedItemKey = value != null
  333. ? getDataCommunicator().getKeyMapper().key(value)
  334. : null;
  335. }
  336. }