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.

AbstractListing.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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.Objects;
  18. import org.jsoup.nodes.Attributes;
  19. import org.jsoup.nodes.Element;
  20. import com.vaadin.data.HasDataProvider;
  21. import com.vaadin.data.HasFilterableDataProvider;
  22. import com.vaadin.data.HasItems;
  23. import com.vaadin.data.provider.DataCommunicator;
  24. import com.vaadin.data.provider.DataGenerator;
  25. import com.vaadin.data.provider.DataProvider;
  26. import com.vaadin.data.provider.Query;
  27. import com.vaadin.server.AbstractExtension;
  28. import com.vaadin.server.Resource;
  29. import com.vaadin.server.SerializableConsumer;
  30. import com.vaadin.shared.extension.abstractlisting.AbstractListingExtensionState;
  31. import com.vaadin.shared.ui.abstractlisting.AbstractListingState;
  32. import com.vaadin.ui.Component.Focusable;
  33. import com.vaadin.ui.declarative.DesignAttributeHandler;
  34. import com.vaadin.ui.declarative.DesignContext;
  35. import com.vaadin.ui.declarative.DesignException;
  36. import com.vaadin.ui.declarative.DesignFormatter;
  37. /**
  38. * A base class for listing components. Provides common handling for fetching
  39. * backend data items, selection logic, and server-client communication.
  40. * <p>
  41. * <strong>Note: </strong> concrete component implementations should implement
  42. * the {@link HasDataProvider} or {@link HasFilterableDataProvider} interface.
  43. *
  44. * @author Vaadin Ltd.
  45. * @since 8.0
  46. *
  47. * @param <T>
  48. * the item data type
  49. *
  50. */
  51. public abstract class AbstractListing<T> extends AbstractComponent
  52. implements Focusable, HasItems<T> {
  53. /**
  54. * The item icon caption provider.
  55. */
  56. private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf;
  57. /**
  58. * The item icon provider. It is up to the implementing class to support
  59. * this or not.
  60. */
  61. private IconGenerator<T> itemIconGenerator = item -> null;
  62. /**
  63. * A helper base class for creating extensions for Listing components. This
  64. * class provides helpers for accessing the underlying parts of the
  65. * component and its communication mechanism.
  66. *
  67. * @param <T>
  68. * the listing item type
  69. */
  70. public abstract static class AbstractListingExtension<T>
  71. extends AbstractExtension implements DataGenerator<T> {
  72. /**
  73. * Adds this extension to the given parent listing.
  74. *
  75. * @param listing
  76. * the parent component to add to
  77. */
  78. public void extend(AbstractListing<T> listing) {
  79. super.extend(listing);
  80. listing.addDataGenerator(this);
  81. }
  82. @Override
  83. public void remove() {
  84. getParent().removeDataGenerator(this);
  85. super.remove();
  86. }
  87. /**
  88. * Gets a data object based on its client-side identifier key.
  89. *
  90. * @param key
  91. * key for data object
  92. * @return the data object
  93. */
  94. protected T getData(String key) {
  95. return getParent().getDataCommunicator().getKeyMapper().get(key);
  96. }
  97. @Override
  98. @SuppressWarnings("unchecked")
  99. public AbstractListing<T> getParent() {
  100. return (AbstractListing<T>) super.getParent();
  101. }
  102. /**
  103. * A helper method for refreshing the client-side representation of a
  104. * single data item.
  105. *
  106. * @param item
  107. * the item to refresh
  108. */
  109. protected void refresh(T item) {
  110. getParent().getDataCommunicator().refresh(item);
  111. }
  112. @Override
  113. protected AbstractListingExtensionState getState() {
  114. return (AbstractListingExtensionState) super.getState();
  115. }
  116. @Override
  117. protected AbstractListingExtensionState getState(boolean markAsDirty) {
  118. return (AbstractListingExtensionState) super.getState(markAsDirty);
  119. }
  120. }
  121. private final DataCommunicator<T> dataCommunicator;
  122. /**
  123. * Creates a new {@code AbstractListing} with a default data communicator.
  124. * <p>
  125. */
  126. protected AbstractListing() {
  127. this(new DataCommunicator<>());
  128. }
  129. /**
  130. * Creates a new {@code AbstractListing} with the given custom data
  131. * communicator.
  132. * <p>
  133. * <strong>Note:</strong> This method is for creating an
  134. * {@code AbstractListing} with a custom communicator. In the common case
  135. * {@link AbstractListing#AbstractListing()} should be used.
  136. * <p>
  137. *
  138. * @param dataCommunicator
  139. * the data communicator to use, not null
  140. */
  141. protected AbstractListing(DataCommunicator<T> dataCommunicator) {
  142. Objects.requireNonNull(dataCommunicator,
  143. "dataCommunicator cannot be null");
  144. this.dataCommunicator = dataCommunicator;
  145. addExtension(dataCommunicator);
  146. }
  147. protected void internalSetDataProvider(DataProvider<T, ?> dataProvider) {
  148. internalSetDataProvider(dataProvider, null);
  149. }
  150. protected <F> SerializableConsumer<F> internalSetDataProvider(
  151. DataProvider<T, F> dataProvider, F initialFilter) {
  152. return getDataCommunicator().setDataProvider(dataProvider,
  153. initialFilter);
  154. }
  155. protected DataProvider<T, ?> internalGetDataProvider() {
  156. return getDataCommunicator().getDataProvider();
  157. }
  158. /**
  159. * Gets the item caption generator that is used to produce the strings shown
  160. * in the combo box for each item.
  161. *
  162. * @return the item caption generator used, not null
  163. */
  164. protected ItemCaptionGenerator<T> getItemCaptionGenerator() {
  165. return itemCaptionGenerator;
  166. }
  167. /**
  168. * Sets the item caption generator that is used to produce the strings shown
  169. * in the combo box for each item. By default,
  170. * {@link String#valueOf(Object)} is used.
  171. *
  172. * @param itemCaptionGenerator
  173. * the item caption provider to use, not null
  174. */
  175. protected void setItemCaptionGenerator(
  176. ItemCaptionGenerator<T> itemCaptionGenerator) {
  177. Objects.requireNonNull(itemCaptionGenerator,
  178. "Item caption generators must not be null");
  179. this.itemCaptionGenerator = itemCaptionGenerator;
  180. getDataCommunicator().reset();
  181. }
  182. /**
  183. * Sets the item icon generator that is used to produce custom icons for
  184. * shown items. The generator can return null for items with no icon.
  185. * <p>
  186. * Implementations that support item icons make this method public.
  187. *
  188. * @see IconGenerator
  189. *
  190. * @param itemIconGenerator
  191. * the item icon generator to set, not null
  192. * @throws NullPointerException
  193. * if {@code itemIconGenerator} is {@code null}
  194. */
  195. protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) {
  196. Objects.requireNonNull(itemIconGenerator,
  197. "Item icon generator must not be null");
  198. this.itemIconGenerator = itemIconGenerator;
  199. getDataCommunicator().reset();
  200. }
  201. /**
  202. * Gets the currently used item icon generator. The default item icon
  203. * provider returns null for all items, resulting in no icons being used.
  204. * <p>
  205. * Implementations that support item icons make this method public.
  206. *
  207. * @see IconGenerator
  208. * @see #setItemIconGenerator(IconGenerator)
  209. *
  210. * @return the currently used item icon generator, not null
  211. */
  212. protected IconGenerator<T> getItemIconGenerator() {
  213. return itemIconGenerator;
  214. }
  215. /**
  216. * Adds the given data generator to this listing. If the generator was
  217. * already added, does nothing.
  218. *
  219. * @param generator
  220. * the data generator to add, not null
  221. */
  222. protected void addDataGenerator(DataGenerator<T> generator) {
  223. getDataCommunicator().addDataGenerator(generator);
  224. }
  225. /**
  226. * Removes the given data generator from this listing. If this listing does
  227. * not have the generator, does nothing.
  228. *
  229. * @param generator
  230. * the data generator to remove, not null
  231. */
  232. protected void removeDataGenerator(DataGenerator<T> generator) {
  233. getDataCommunicator().removeDataGenerator(generator);
  234. }
  235. /**
  236. * Returns the data communicator of this listing.
  237. *
  238. * @return the data communicator, not null
  239. */
  240. public DataCommunicator<T> getDataCommunicator() {
  241. return dataCommunicator;
  242. }
  243. @Override
  244. public void writeDesign(Element design, DesignContext designContext) {
  245. super.writeDesign(design, designContext);
  246. doWriteDesign(design, designContext);
  247. }
  248. /**
  249. * Writes listing specific state into the given design.
  250. * <p>
  251. * This method is separated from
  252. * {@link #writeDesign(Element, DesignContext)} to be overridable in
  253. * subclasses that need to replace this, but still must be able to call
  254. * {@code super.writeDesign(...)}.
  255. *
  256. * @see #doReadDesign(Element, DesignContext)
  257. *
  258. * @param design
  259. * The element to write the component state to. Any previous
  260. * attributes or child nodes are <i>not</i> cleared.
  261. * @param designContext
  262. * The DesignContext instance used for writing the design
  263. *
  264. */
  265. protected void doWriteDesign(Element design, DesignContext designContext) {
  266. // Write options if warranted
  267. if (designContext.shouldWriteData(this)) {
  268. writeItems(design, designContext);
  269. }
  270. AbstractListing<T> select = designContext.getDefaultInstance(this);
  271. Attributes attr = design.attributes();
  272. DesignAttributeHandler.writeAttribute("readonly", attr, isReadOnly(),
  273. select.isReadOnly(), Boolean.class, designContext);
  274. }
  275. /**
  276. * Writes the data source items to a design. Hierarchical select components
  277. * should override this method to only write the root items.
  278. *
  279. * @param design
  280. * the element into which to insert the items
  281. * @param context
  282. * the DesignContext instance used in writing
  283. */
  284. protected void writeItems(Element design, DesignContext context) {
  285. internalGetDataProvider().fetch(new Query<>())
  286. .forEach(item -> writeItem(design, item, context));
  287. }
  288. /**
  289. * Writes a data source Item to a design. Hierarchical select components
  290. * should override this method to recursively write any child items as well.
  291. *
  292. * @param design
  293. * the element into which to insert the item
  294. * @param item
  295. * the item to write
  296. * @param context
  297. * the DesignContext instance used in writing
  298. * @return a JSOUP element representing the {@code item}
  299. */
  300. protected Element writeItem(Element design, T item, DesignContext context) {
  301. Element element = design.appendElement("option");
  302. String caption = getItemCaptionGenerator().apply(item);
  303. if (caption != null) {
  304. element.html(DesignFormatter.encodeForTextNode(caption));
  305. } else {
  306. element.html(DesignFormatter.encodeForTextNode(item.toString()));
  307. }
  308. element.attr("item", serializeDeclarativeRepresentation(item));
  309. Resource icon = getItemIconGenerator().apply(item);
  310. if (icon != null) {
  311. DesignAttributeHandler.writeAttribute("icon", element.attributes(),
  312. icon, null, Resource.class, context);
  313. }
  314. return element;
  315. }
  316. @Override
  317. public void readDesign(Element design, DesignContext context) {
  318. super.readDesign(design, context);
  319. doReadDesign(design, context);
  320. }
  321. /**
  322. * Reads the listing specific state from the given design.
  323. * <p>
  324. * This method is separated from {@link #readDesign(Element, DesignContext)}
  325. * to be overridable in subclasses that need to replace this, but still must
  326. * be able to call {@code super.readDesign(...)}.
  327. *
  328. * @see #doWriteDesign(Element, DesignContext)
  329. *
  330. * @param design
  331. * The element to obtain the state from
  332. * @param context
  333. * The DesignContext instance used for parsing the design
  334. */
  335. protected void doReadDesign(Element design, DesignContext context) {
  336. Attributes attr = design.attributes();
  337. if (attr.hasKey("readonly")) {
  338. setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr,
  339. Boolean.class));
  340. }
  341. setItemCaptionGenerator(
  342. new DeclarativeCaptionGenerator<>(getItemCaptionGenerator()));
  343. setItemIconGenerator(
  344. new DeclarativeIconGenerator<>(getItemIconGenerator()));
  345. readItems(design, context);
  346. }
  347. /**
  348. * Reads the data source items from the {@code design}.
  349. *
  350. * @param design
  351. * The element to obtain the state from
  352. * @param context
  353. * The DesignContext instance used for parsing the design
  354. */
  355. protected abstract void readItems(Element design, DesignContext context);
  356. /**
  357. * Reads an Item from a design and inserts it into the data source.
  358. * <p>
  359. * Doesn't care about selection/value (if any).
  360. *
  361. * @param child
  362. * a child element representing the item
  363. * @param context
  364. * the DesignContext instance used in parsing
  365. * @return the item id of the new item
  366. *
  367. * @throws DesignException
  368. * if the tag name of the {@code child} element is not
  369. * {@code option}.
  370. */
  371. @SuppressWarnings({ "rawtypes", "unchecked" })
  372. protected T readItem(Element child, DesignContext context) {
  373. if (!"option".equals(child.tagName())) {
  374. throw new DesignException("Unrecognized child element in "
  375. + getClass().getSimpleName() + ": " + child.tagName());
  376. }
  377. String serializedItem = "";
  378. String caption = DesignFormatter.decodeFromTextNode(child.html());
  379. if (child.hasAttr("item")) {
  380. serializedItem = child.attr("item");
  381. }
  382. T item = deserializeDeclarativeRepresentation(serializedItem);
  383. ItemCaptionGenerator<T> captionGenerator = getItemCaptionGenerator();
  384. if (captionGenerator instanceof DeclarativeCaptionGenerator) {
  385. ((DeclarativeCaptionGenerator) captionGenerator).setCaption(item,
  386. caption);
  387. } else {
  388. throw new IllegalStateException(String.format("Don't know how "
  389. + "to set caption using current caption generator '%s'",
  390. captionGenerator.getClass().getName()));
  391. }
  392. IconGenerator<T> iconGenerator = getItemIconGenerator();
  393. if (child.hasAttr("icon")) {
  394. if (iconGenerator instanceof DeclarativeIconGenerator) {
  395. ((DeclarativeIconGenerator) iconGenerator).setIcon(item,
  396. DesignAttributeHandler.readAttribute("icon",
  397. child.attributes(), Resource.class));
  398. } else {
  399. throw new IllegalStateException(String.format("Don't know how "
  400. + "to set icon using current caption generator '%s'",
  401. iconGenerator.getClass().getName()));
  402. }
  403. }
  404. return item;
  405. }
  406. /**
  407. * Deserializes a string to a data item.
  408. * <p>
  409. * Default implementation is able to handle only {@link String} as an item
  410. * type. There will be a {@link ClassCastException} if {@code T } is not a
  411. * {@link String}.
  412. *
  413. * @see #serializeDeclarativeRepresentation(Object)
  414. *
  415. * @param item
  416. * string to deserialize
  417. * @throws ClassCastException
  418. * if type {@code T} is not a {@link String}
  419. * @return deserialized item
  420. */
  421. protected T deserializeDeclarativeRepresentation(String item) {
  422. return (T) item;
  423. }
  424. /**
  425. * Serializes an {@code item} to a string for saving declarative format.
  426. * <p>
  427. * Default implementation delegates a call to {@code item.toString()}.
  428. *
  429. * @see #deserializeDeclarativeRepresentation(String)
  430. *
  431. * @param item
  432. * a data item
  433. * @return string representation of the {@code item}.
  434. */
  435. protected String serializeDeclarativeRepresentation(T item) {
  436. return item.toString();
  437. }
  438. @Override
  439. protected AbstractListingState getState() {
  440. return (AbstractListingState) super.getState();
  441. }
  442. @Override
  443. protected AbstractListingState getState(boolean markAsDirty) {
  444. return (AbstractListingState) super.getState(markAsDirty);
  445. }
  446. @Override
  447. public void focus() {
  448. super.focus();
  449. }
  450. @Override
  451. public int getTabIndex() {
  452. return getState(false).tabIndex;
  453. }
  454. @Override
  455. public void setTabIndex(int tabIndex) {
  456. getState().tabIndex = tabIndex;
  457. }
  458. }