您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

AbstractSingleSelect.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /*
  2. * Copyright 2000-2016 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.event.selection.SingleSelectionEvent;
  29. import com.vaadin.event.selection.SingleSelectionListener;
  30. import com.vaadin.shared.Registration;
  31. import com.vaadin.shared.data.selection.SelectionServerRpc;
  32. import com.vaadin.shared.ui.AbstractSingleSelectState;
  33. import com.vaadin.ui.declarative.DesignContext;
  34. import com.vaadin.ui.declarative.DesignException;
  35. import elemental.json.Json;
  36. /**
  37. * An abstract base class for listing components that only support single
  38. * selection and no lazy loading of data items.
  39. *
  40. * @author Vaadin Ltd.
  41. *
  42. * @param <T>
  43. * the item date type
  44. *
  45. * @see com.vaadin.data.SelectionModel.Single
  46. *
  47. * @since 8.0
  48. */
  49. public abstract class AbstractSingleSelect<T> extends AbstractListing<T>
  50. implements SingleSelect<T> {
  51. /**
  52. * Creates a new {@code AbstractListing} with a default data communicator.
  53. * <p>
  54. */
  55. protected AbstractSingleSelect() {
  56. init();
  57. }
  58. /**
  59. * Creates a new {@code AbstractSingleSelect} with the given custom data
  60. * communicator.
  61. * <p>
  62. * <strong>Note:</strong> This method is for creating an
  63. * {@code AbstractSingleSelect} with a custom communicator. In the common
  64. * case {@link AbstractSingleSelect#AbstractSingleSelect()} should be used.
  65. * <p>
  66. *
  67. * @param dataCommunicator
  68. * the data communicator to use, not null
  69. */
  70. protected AbstractSingleSelect(DataCommunicator<T> dataCommunicator) {
  71. super(dataCommunicator);
  72. init();
  73. }
  74. /**
  75. * Adds a selection listener to this select. The listener is called when the
  76. * selection is changed either by the user or programmatically.
  77. *
  78. * @param listener
  79. * the selection listener, not null
  80. * @return a registration for the listener
  81. */
  82. public Registration addSelectionListener(
  83. SingleSelectionListener<T> listener) {
  84. return addListener(SingleSelectionEvent.class, listener,
  85. SingleSelectionListener.SELECTION_CHANGE_METHOD);
  86. }
  87. /**
  88. * Returns the currently selected item, or an empty optional if no item is
  89. * selected.
  90. *
  91. * @return an optional of the selected item if any, an empty optional
  92. * otherwise
  93. */
  94. public Optional<T> getSelectedItem() {
  95. return Optional.ofNullable(keyToItem(getSelectedKey()));
  96. }
  97. /**
  98. * Sets the current selection to the given item or clears selection if given
  99. * {@code null}.
  100. *
  101. * @param item
  102. * the item to select or {@code null} to clear selection
  103. */
  104. public void setSelectedItem(T item) {
  105. setSelectedFromServer(item);
  106. }
  107. /**
  108. * Returns the current value of this object which is the currently selected
  109. * item.
  110. * <p>
  111. * The call is delegated to {@link #getSelectedItem()}
  112. *
  113. * @return the current selection, may be {@code null}
  114. *
  115. * @see #getSelectedItem()
  116. * @see Single#getSelectedItem
  117. */
  118. @Override
  119. public T getValue() {
  120. return getSelectedItem().orElse(null);
  121. }
  122. /**
  123. * Sets the value of this object which is an item to select. If the new
  124. * value is not equal to {@code getValue()}, fires a value change event. If
  125. * value is {@code null} then it deselects currently selected item.
  126. * <p>
  127. * The call is delegated to {@link #setSelectedItem(Object)}.
  128. *
  129. * @see #setSelectedItem(Object)
  130. * @see Single#setSelectedItem(Object)
  131. *
  132. * @param value
  133. * the item to select or {@code null} to clear selection
  134. */
  135. @Override
  136. public void setValue(T value) {
  137. setSelectedItem(value);
  138. }
  139. @Override
  140. public Registration addValueChangeListener(
  141. HasValue.ValueChangeListener<T> listener) {
  142. return addSelectionListener(
  143. event -> listener.valueChange(new ValueChangeEvent<>(this,
  144. event.getOldValue(), event.isUserOriginated())));
  145. }
  146. @Override
  147. protected AbstractSingleSelectState getState() {
  148. return (AbstractSingleSelectState) super.getState();
  149. }
  150. @Override
  151. protected AbstractSingleSelectState getState(boolean markAsDirty) {
  152. return (AbstractSingleSelectState) super.getState(markAsDirty);
  153. }
  154. @Override
  155. public void setRequiredIndicatorVisible(boolean visible) {
  156. super.setRequiredIndicatorVisible(visible);
  157. }
  158. @Override
  159. public boolean isRequiredIndicatorVisible() {
  160. return super.isRequiredIndicatorVisible();
  161. }
  162. @Override
  163. public void setReadOnly(boolean readOnly) {
  164. super.setReadOnly(readOnly);
  165. }
  166. @Override
  167. public boolean isReadOnly() {
  168. return super.isReadOnly();
  169. }
  170. /**
  171. * Returns the communication key of the selected item or {@code null} if no
  172. * item is selected.
  173. *
  174. * @return the key of the selected item if any, {@code null} otherwise.
  175. */
  176. protected String getSelectedKey() {
  177. return getState(false).selectedItemKey;
  178. }
  179. /**
  180. * Sets the selected item based on the given communication key. If the key
  181. * is {@code null}, clears the current selection if any.
  182. *
  183. * @param key
  184. * the key of the selected item or {@code null} to clear
  185. * selection
  186. */
  187. protected void doSetSelectedKey(String key) {
  188. getState().selectedItemKey = key;
  189. }
  190. /**
  191. * Sets the selection based on a client request. Does nothing if the select
  192. * component is {@linkplain #isReadOnly()} or if the selection would not
  193. * change. Otherwise updates the selection and fires a selection change
  194. * event with {@code isUserOriginated == true}.
  195. *
  196. * @param key
  197. * the key of the item to select or {@code null} to clear
  198. * selection
  199. */
  200. protected void setSelectedFromClient(String key) {
  201. if (isReadOnly()) {
  202. return;
  203. }
  204. if (isKeySelected(key)) {
  205. return;
  206. }
  207. T oldSelection = getSelectedItem().orElse(getEmptyValue());
  208. doSetSelectedKey(key);
  209. // Set diffstate to something that will always send selection to client
  210. updateDiffstate("selectedItemKey", Json.createObject());
  211. fireEvent(new SingleSelectionEvent<>(AbstractSingleSelect.this,
  212. oldSelection, true));
  213. }
  214. /**
  215. * Sets the selection based on server API call. Does nothing if the
  216. * selection would not change; otherwise updates the selection and fires a
  217. * selection change event with {@code isUserOriginated == false}.
  218. *
  219. * @param item
  220. * the item to select or {@code null} to clear selection
  221. */
  222. protected void setSelectedFromServer(T item) {
  223. // TODO creates a key if item not in data provider
  224. String key = itemToKey(item);
  225. if (isKeySelected(key) || isSelected(item)) {
  226. return;
  227. }
  228. T oldSelection = getSelectedItem().orElse(getEmptyValue());
  229. doSetSelectedKey(key);
  230. fireEvent(new SingleSelectionEvent<>(AbstractSingleSelect.this,
  231. oldSelection, false));
  232. }
  233. /**
  234. * Returns whether the given key maps to the currently selected item.
  235. *
  236. * @param key
  237. * the key to test or {@code null} to test whether nothing is
  238. * selected
  239. * @return {@code true} if the key equals the key of the currently selected
  240. * item (or {@code null} if no selection), {@code false} otherwise.
  241. */
  242. protected boolean isKeySelected(String key) {
  243. return Objects.equals(key, getSelectedKey());
  244. }
  245. /**
  246. * Returns the communication key assigned to the given item.
  247. *
  248. * @param item
  249. * the item whose key to return
  250. * @return the assigned key
  251. */
  252. protected String itemToKey(T item) {
  253. if (item == null) {
  254. return null;
  255. } else {
  256. // TODO creates a key if item not in data provider
  257. return getDataCommunicator().getKeyMapper().key(item);
  258. }
  259. }
  260. /**
  261. * Returns the item that the given key is assigned to, or {@code null} if
  262. * there is no such item.
  263. *
  264. * @param key
  265. * the key whose item to return
  266. * @return the associated item if any, {@code null} otherwise.
  267. */
  268. protected T keyToItem(String key) {
  269. return getDataCommunicator().getKeyMapper().get(key);
  270. }
  271. /**
  272. * Returns whether the given item is currently selected.
  273. *
  274. * @param item
  275. * the item to check, not null
  276. * @return {@code true} if the item is selected, {@code false} otherwise
  277. */
  278. public boolean isSelected(T item) {
  279. return Objects.equals(getValue(), item);
  280. }
  281. @Override
  282. protected Element writeItem(Element design, T item, DesignContext context) {
  283. Element element = super.writeItem(design, item, context);
  284. if (isSelected(item)) {
  285. element.attr("selected", true);
  286. }
  287. return element;
  288. }
  289. @Override
  290. protected void readItems(Element design, DesignContext context) {
  291. Set<T> selected = new HashSet<>();
  292. List<T> items = design.children().stream()
  293. .map(child -> readItem(child, selected, context))
  294. .collect(Collectors.toList());
  295. if (!items.isEmpty()) {
  296. setItems(items);
  297. }
  298. selected.forEach(this::setValue);
  299. }
  300. /**
  301. * Reads an Item from a design and inserts it into the data source.
  302. * Hierarchical select components should override this method to recursively
  303. * recursively read any child items as well.
  304. *
  305. * @param child
  306. * a child element representing the item
  307. * @param selected
  308. * A set accumulating selected items. If the item that is read is
  309. * marked as selected, its item id should be added to this set.
  310. * @param context
  311. * the DesignContext instance used in parsing
  312. * @return the item id of the new item
  313. *
  314. * @throws DesignException
  315. * if the tag name of the {@code child} element is not
  316. * {@code option}.
  317. */
  318. protected T readItem(Element child, Set<T> selected,
  319. DesignContext context) {
  320. T item = readItem(child, context);
  321. if (child.hasAttr("selected")) {
  322. selected.add(item);
  323. }
  324. return item;
  325. }
  326. @Override
  327. protected Collection<String> getCustomAttributes() {
  328. Collection<String> attributes = super.getCustomAttributes();
  329. // "value" is not an attribute for the component. "selected" attribute
  330. // is used in "option"'s tag to mark selection which implies value for
  331. // single select component
  332. attributes.add("value");
  333. return attributes;
  334. }
  335. private void init() {
  336. registerRpc(new SelectionServerRpc() {
  337. @Override
  338. public void select(String key) {
  339. setSelectedFromClient(key);
  340. }
  341. @Override
  342. public void deselect(String key) {
  343. if (isKeySelected(key)) {
  344. setSelectedFromClient(null);
  345. }
  346. }
  347. });
  348. }
  349. }