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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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.List;
  18. import java.util.Objects;
  19. import org.jsoup.nodes.Attributes;
  20. import org.jsoup.nodes.Element;
  21. import com.vaadin.data.Listing;
  22. import com.vaadin.data.SelectionModel;
  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.shared.extension.abstractlisting.AbstractListingExtensionState;
  30. import com.vaadin.shared.ui.abstractlisting.AbstractListingState;
  31. import com.vaadin.ui.Component.Focusable;
  32. import com.vaadin.ui.declarative.DesignAttributeHandler;
  33. import com.vaadin.ui.declarative.DesignContext;
  34. import com.vaadin.ui.declarative.DesignException;
  35. import com.vaadin.ui.declarative.DesignFormatter;
  36. /**
  37. * A base class for listing components. Provides common handling for fetching
  38. * backend data items, selection logic, and server-client communication.
  39. * <p>
  40. * <strong>Note: </strong> concrete component implementations should implement
  41. * the {@link Listing} interface.
  42. *
  43. * @author Vaadin Ltd.
  44. * @since 8.0
  45. *
  46. * @param <T>
  47. * the item data type
  48. *
  49. * @see Listing
  50. */
  51. public abstract class AbstractListing<T> extends AbstractComponent
  52. implements Focusable {
  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. * <strong>Note:</strong> This constructor does not set a selection model
  126. * for the new listing. The invoking constructor must explicitly call
  127. * {@link #setSelectionModel(SelectionModel)}.
  128. */
  129. protected AbstractListing() {
  130. this(new DataCommunicator<>());
  131. }
  132. /**
  133. * Creates a new {@code AbstractListing} with the given custom data
  134. * communicator.
  135. * <p>
  136. * <strong>Note:</strong> This method is for creating an
  137. * {@code AbstractListing} with a custom communicator. In the common case
  138. * {@link AbstractListing#AbstractListing()} should be used.
  139. * <p>
  140. * <strong>Note:</strong> This constructor does not set a selection model
  141. * for the new listing. The invoking constructor must explicitly call
  142. * {@link #setSelectionModel(SelectionModel)}.
  143. *
  144. * @param dataCommunicator
  145. * the data communicator to use, not null
  146. */
  147. protected AbstractListing(DataCommunicator<T, ?> dataCommunicator) {
  148. Objects.requireNonNull(dataCommunicator,
  149. "dataCommunicator cannot be null");
  150. this.dataCommunicator = dataCommunicator;
  151. addExtension(dataCommunicator);
  152. }
  153. @SuppressWarnings({ "rawtypes", "unchecked" })
  154. protected void internalSetDataProvider(DataProvider<T, ?> dataProvider) {
  155. // cast needed by compiler
  156. getDataCommunicator().setDataProvider((DataProvider) dataProvider);
  157. }
  158. protected DataProvider<T, ?> internalGetDataProvider() {
  159. return getDataCommunicator().getDataProvider();
  160. }
  161. /**
  162. * Gets the item caption generator that is used to produce the strings shown
  163. * in the combo box for each item.
  164. *
  165. * @return the item caption generator used, not null
  166. */
  167. protected ItemCaptionGenerator<T> getItemCaptionGenerator() {
  168. return itemCaptionGenerator;
  169. }
  170. /**
  171. * Sets the item caption generator that is used to produce the strings shown
  172. * in the combo box for each item. By default,
  173. * {@link String#valueOf(Object)} is used.
  174. *
  175. * @param itemCaptionGenerator
  176. * the item caption provider to use, not null
  177. */
  178. protected void setItemCaptionGenerator(
  179. ItemCaptionGenerator<T> itemCaptionGenerator) {
  180. Objects.requireNonNull(itemCaptionGenerator,
  181. "Item caption generators must not be null");
  182. this.itemCaptionGenerator = itemCaptionGenerator;
  183. getDataCommunicator().reset();
  184. }
  185. /**
  186. * Sets the item icon generator that is used to produce custom icons for
  187. * showing items in the popup. The generator can return null for items with
  188. * no icon.
  189. *
  190. * @see IconGenerator
  191. *
  192. * @param itemIconGenerator
  193. * the item icon generator to set, not null
  194. * @throws NullPointerException
  195. * if {@code itemIconGenerator} is {@code null}
  196. */
  197. protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) {
  198. Objects.requireNonNull(itemIconGenerator,
  199. "Item icon generator must not be null");
  200. this.itemIconGenerator = itemIconGenerator;
  201. getDataCommunicator().reset();
  202. }
  203. /**
  204. * Gets the currently used item icon generator. The default item icon
  205. * provider returns null for all items, resulting in no icons being used.
  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 {@link writeDesign(Element, DesignContext)}
  252. * to be overridable in subclasses that need to replace this, but still must
  253. * be able to call {@code super.writeDesign(...)}.
  254. *
  255. * @see #doReadDesign(Element, DesignContext)
  256. *
  257. * @param design
  258. * The element to write the component state to. Any previous
  259. * attributes or child nodes are <i>not</i> cleared.
  260. * @param designContext
  261. * The DesignContext instance used for writing the design
  262. *
  263. */
  264. protected void doWriteDesign(Element design, DesignContext designContext) {
  265. // Write options if warranted
  266. if (designContext.shouldWriteData(this)) {
  267. writeItems(design, designContext);
  268. }
  269. AbstractListing<T> select = designContext.getDefaultInstance(this);
  270. Attributes attr = design.attributes();
  271. DesignAttributeHandler.writeAttribute("readonly", attr, isReadOnly(),
  272. select.isReadOnly(), Boolean.class, designContext);
  273. }
  274. /**
  275. * Writes the data source items to a design. Hierarchical select components
  276. * should override this method to only write the root items.
  277. *
  278. * @param design
  279. * the element into which to insert the items
  280. * @param context
  281. * the DesignContext instance used in writing
  282. */
  283. protected void writeItems(Element design, DesignContext context) {
  284. internalGetDataProvider().fetch(new Query<>())
  285. .forEach(item -> writeItem(design, item, context));
  286. }
  287. /**
  288. * Writes a data source Item to a design. Hierarchical select components
  289. * should override this method to recursively write any child items as well.
  290. *
  291. * @param design
  292. * the element into which to insert the item
  293. * @param item
  294. * the item to write
  295. * @param context
  296. * the DesignContext instance used in writing
  297. * @return a JSOUP element representing the {@code item}
  298. */
  299. protected Element writeItem(Element design, T item, DesignContext context) {
  300. Element element = design.appendElement("option");
  301. String caption = getItemCaptionGenerator().apply(item);
  302. if (caption != null) {
  303. element.html(DesignFormatter.encodeForTextNode(caption));
  304. } else {
  305. element.html(DesignFormatter.encodeForTextNode(item.toString()));
  306. }
  307. element.attr("item", serializeDeclarativeRepresentation(item));
  308. Resource icon = getItemIconGenerator().apply(item);
  309. if (icon != null) {
  310. DesignAttributeHandler.writeAttribute("icon", element.attributes(),
  311. icon, null, Resource.class, context);
  312. }
  313. return element;
  314. }
  315. @Override
  316. public void readDesign(Element design, DesignContext context) {
  317. super.readDesign(design, context);
  318. doReadDesign(design, context);
  319. }
  320. /**
  321. * Reads the listing specific state from the given design.
  322. * <p>
  323. * This method is separated from {@link readDesign(Element, DesignContext)}
  324. * to be overridable in subclasses that need to replace this, but still must
  325. * be able to call {@code super.readDesign(...)}.
  326. *
  327. * @see #doWriteDesign(Element, DesignContext)
  328. *
  329. * @param design
  330. * The element to obtain the state from
  331. * @param context
  332. * The DesignContext instance used for parsing the design
  333. */
  334. protected void doReadDesign(Element design, DesignContext context) {
  335. Attributes attr = design.attributes();
  336. if (attr.hasKey("readonly")) {
  337. setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr,
  338. Boolean.class));
  339. }
  340. setItemCaptionGenerator(
  341. new DeclarativeCaptionGenerator<>(getItemCaptionGenerator()));
  342. setItemIconGenerator(
  343. new DeclarativeIconGenerator<>(getItemIconGenerator()));
  344. List<T> readItems = readItems(design, context);
  345. if (!readItems.isEmpty() && this instanceof Listing) {
  346. ((Listing<T, ?>) this).setItems(readItems);
  347. }
  348. }
  349. /**
  350. * Reads the data source items from the {@code design}.
  351. *
  352. * @param design
  353. * The element to obtain the state from
  354. * @param context
  355. * The DesignContext instance used for parsing the design
  356. *
  357. * @return the items read from the design
  358. */
  359. protected abstract List<T> readItems(Element design, DesignContext context);
  360. /**
  361. * Reads an Item from a design and inserts it into the data source.
  362. * <p>
  363. * Doesn't care about selection/value (if any).
  364. *
  365. * @param child
  366. * a child element representing the item
  367. * @param context
  368. * the DesignContext instance used in parsing
  369. * @return the item id of the new item
  370. *
  371. * @throws DesignException
  372. * if the tag name of the {@code child} element is not
  373. * {@code option}.
  374. */
  375. @SuppressWarnings({ "rawtypes", "unchecked" })
  376. protected T readItem(Element child, DesignContext context) {
  377. if (!"option".equals(child.tagName())) {
  378. throw new DesignException("Unrecognized child element in "
  379. + getClass().getSimpleName() + ": " + child.tagName());
  380. }
  381. String serializedItem = "";
  382. String caption = DesignFormatter.decodeFromTextNode(child.html());
  383. if (child.hasAttr("item")) {
  384. serializedItem = child.attr("item");
  385. }
  386. T item = deserializeDeclarativeRepresentation(serializedItem);
  387. ItemCaptionGenerator<T> captionGenerator = getItemCaptionGenerator();
  388. if (captionGenerator instanceof DeclarativeCaptionGenerator) {
  389. ((DeclarativeCaptionGenerator) captionGenerator).setCaption(item,
  390. caption);
  391. } else {
  392. throw new IllegalStateException(String.format(
  393. "Don't know how "
  394. + "to set caption using current caption generator '%s'",
  395. captionGenerator.getClass().getName()));
  396. }
  397. IconGenerator<T> iconGenerator = getItemIconGenerator();
  398. if (child.hasAttr("icon")) {
  399. if (iconGenerator instanceof DeclarativeIconGenerator) {
  400. ((DeclarativeIconGenerator) iconGenerator).setIcon(item,
  401. DesignAttributeHandler.readAttribute("icon",
  402. child.attributes(), Resource.class));
  403. } else {
  404. throw new IllegalStateException(String.format(
  405. "Don't know how "
  406. + "to set icon using current caption generator '%s'",
  407. iconGenerator.getClass().getName()));
  408. }
  409. }
  410. return item;
  411. }
  412. /**
  413. * Deserializes a string to a data item.
  414. * <p>
  415. * Default implementation is able to handle only {@link String} as an item
  416. * type. There will be a {@link ClassCastException} if {@code T } is not a
  417. * {@link String}.
  418. *
  419. * @see #serializeDeclarativeRepresentation(Object)
  420. *
  421. * @param item
  422. * string to deserialize
  423. * @throws ClassCastException
  424. * if type {@code T} is not a {@link String}
  425. * @return deserialized item
  426. */
  427. protected T deserializeDeclarativeRepresentation(String item) {
  428. return (T) item;
  429. }
  430. /**
  431. * Serializes an {@code item} to a string for saving declarative format.
  432. * <p>
  433. * Default implementation delegates a call to {@code item.toString()}.
  434. *
  435. * @see #deserializeDeclarativeRepresentation(String)
  436. *
  437. * @param item
  438. * a data item
  439. * @return string representation of the {@code item}.
  440. */
  441. protected String serializeDeclarativeRepresentation(T item) {
  442. return item.toString();
  443. }
  444. @Override
  445. protected AbstractListingState getState() {
  446. return (AbstractListingState) super.getState();
  447. }
  448. @Override
  449. protected AbstractListingState getState(boolean markAsDirty) {
  450. return (AbstractListingState) super.getState(markAsDirty);
  451. }
  452. @Override
  453. public void focus() {
  454. super.focus();
  455. }
  456. @Override
  457. public int getTabIndex() {
  458. return getState(false).tabIndex;
  459. }
  460. @Override
  461. public void setTabIndex(int tabIndex) {
  462. getState().tabIndex = tabIndex;
  463. }
  464. }