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.

EditorImpl.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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.components.grid;
  17. import java.util.Collections;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Objects;
  22. import java.util.stream.Collectors;
  23. import java.util.stream.Stream;
  24. import com.vaadin.data.Binder;
  25. import com.vaadin.data.Binder.Binding;
  26. import com.vaadin.data.BinderValidationStatus;
  27. import com.vaadin.data.BinderValidationStatusHandler;
  28. import com.vaadin.data.PropertySet;
  29. import com.vaadin.event.EventRouter;
  30. import com.vaadin.shared.Registration;
  31. import com.vaadin.shared.ui.grid.editor.EditorClientRpc;
  32. import com.vaadin.shared.ui.grid.editor.EditorServerRpc;
  33. import com.vaadin.shared.ui.grid.editor.EditorState;
  34. import com.vaadin.ui.Component;
  35. import com.vaadin.ui.Grid;
  36. import com.vaadin.ui.Grid.AbstractGridExtension;
  37. import com.vaadin.ui.Grid.Column;
  38. import com.vaadin.util.ReflectTools;
  39. import elemental.json.JsonObject;
  40. /**
  41. * Implementation of {@code Editor} interface.
  42. *
  43. * @param <T>
  44. * the grid bean type
  45. * @since 8.0
  46. */
  47. public class EditorImpl<T> extends AbstractGridExtension<T>
  48. implements Editor<T> {
  49. private class EditorStatusHandler
  50. implements BinderValidationStatusHandler<T> {
  51. @Override
  52. public void statusChange(BinderValidationStatus<T> status) {
  53. boolean ok = status.isOk();
  54. if (saving) {
  55. rpc.confirmSave(ok);
  56. saving = false;
  57. }
  58. if (ok) {
  59. if (binder.getBean() != null) {
  60. refresh(binder.getBean());
  61. }
  62. rpc.setErrorMessage(null, Collections.emptyList());
  63. } else {
  64. List<Component> fields = status.getFieldValidationErrors()
  65. .stream().map(error -> error.getField())
  66. .filter(columnFields.values()::contains)
  67. .map(field -> (Component) field)
  68. .collect(Collectors.toList());
  69. Map<Component, Column<T, ?>> fieldToColumn = new HashMap<>();
  70. columnFields.entrySet().stream()
  71. .filter(entry -> fields.contains(entry.getValue()))
  72. .forEach(entry -> fieldToColumn.put(entry.getValue(),
  73. entry.getKey()));
  74. String message = errorGenerator.apply(fieldToColumn, status);
  75. List<String> columnIds = fieldToColumn.values().stream()
  76. .map(column -> getInternalIdForColumn(column))
  77. .collect(Collectors.toList());
  78. rpc.setErrorMessage(message, columnIds);
  79. }
  80. }
  81. }
  82. private Binder<T> binder;
  83. private Map<Column<T, ?>, Component> columnFields = new HashMap<>();
  84. private T edited;
  85. private boolean saving = false;
  86. private EditorClientRpc rpc;
  87. private EventRouter eventRouter = new EventRouter();
  88. private EditorErrorGenerator<T> errorGenerator = (fieldToColumn,
  89. status) -> {
  90. String message = status.getFieldValidationErrors().stream()
  91. .filter(e -> e.getMessage().isPresent()
  92. && fieldToColumn.containsKey(e.getField()))
  93. .map(e -> fieldToColumn.get(e.getField()).getCaption() + ": "
  94. + e.getMessage().get())
  95. .collect(Collectors.joining("; "));
  96. String beanMessage = status.getBeanValidationErrors().stream()
  97. .map(e -> e.getErrorMessage())
  98. .collect(Collectors.joining("; "));
  99. message = Stream.of(message, beanMessage).filter(s -> !s.isEmpty())
  100. .collect(Collectors.joining("; "));
  101. return message;
  102. };
  103. /**
  104. * Constructor for internal implementation of the Editor.
  105. *
  106. * @param propertySet
  107. * the property set to use for configuring the default binder
  108. */
  109. public EditorImpl(PropertySet<T> propertySet) {
  110. rpc = getRpcProxy(EditorClientRpc.class);
  111. registerRpc(new EditorServerRpc() {
  112. @Override
  113. public void save() {
  114. saving = true;
  115. EditorImpl.this.save();
  116. }
  117. @Override
  118. public void cancel(boolean afterBeingSaved) {
  119. doCancel(afterBeingSaved);
  120. }
  121. @Override
  122. public void bind(String key) {
  123. // When in buffered mode, the editor is not allowed to move.
  124. // Binder with failed validation returns true for hasChanges.
  125. if (isOpen() && (isBuffered() || getBinder().hasChanges())) {
  126. rpc.confirmBind(false);
  127. return;
  128. }
  129. doClose();
  130. doEdit(getData(key));
  131. rpc.confirmBind(true);
  132. }
  133. });
  134. setBinder(Binder.withPropertySet(propertySet));
  135. }
  136. @Override
  137. public void generateData(T item, JsonObject jsonObject) {
  138. }
  139. @Override
  140. public Editor<T> setBinder(Binder<T> binder) {
  141. this.binder = binder;
  142. binder.setValidationStatusHandler(new EditorStatusHandler());
  143. return this;
  144. }
  145. @Override
  146. public Binder<T> getBinder() {
  147. return binder;
  148. }
  149. @Override
  150. public Editor<T> setBuffered(boolean buffered) {
  151. if (isOpen()) {
  152. throw new IllegalStateException(
  153. "Cannot modify Editor when it is open.");
  154. }
  155. getState().buffered = buffered;
  156. return this;
  157. }
  158. @Override
  159. public Editor<T> setEnabled(boolean enabled) {
  160. if (isOpen()) {
  161. throw new IllegalStateException(
  162. "Cannot modify Editor when it is open.");
  163. }
  164. getState().enabled = enabled;
  165. return this;
  166. }
  167. @Override
  168. public boolean isBuffered() {
  169. return getState(false).buffered;
  170. }
  171. @Override
  172. public boolean isEnabled() {
  173. return getState(false).enabled;
  174. }
  175. /**
  176. * Handles editor component generation and adding them to the hierarchy of
  177. * the Grid.
  178. *
  179. * @param bean
  180. * the edited item; can't be {@code null}
  181. */
  182. protected void doEdit(T bean) {
  183. Objects.requireNonNull(bean, "Editor can't edit null");
  184. if (!isEnabled()) {
  185. throw new IllegalStateException(
  186. "Editing is not allowed when Editor is disabled.");
  187. }
  188. if (!isBuffered()) {
  189. binder.setBean(bean);
  190. } else {
  191. binder.readBean(bean);
  192. }
  193. edited = bean;
  194. getParent().getColumns().stream().filter(Column::isEditable)
  195. .forEach(c -> {
  196. Binding<T, ?> binding = c.getEditorBinding();
  197. assert binding
  198. .getField() instanceof Component : "Grid should enforce that the binding field is a component";
  199. Component component = (Component) binding.getField();
  200. addComponentToGrid(component);
  201. columnFields.put(c, component);
  202. getState().columnFields.put(getInternalIdForColumn(c),
  203. component.getConnectorId());
  204. });
  205. eventRouter.fireEvent(new EditorOpenEvent<T>(this, edited));
  206. }
  207. @Override
  208. public boolean save() {
  209. if (isOpen() && isBuffered()) {
  210. binder.validate();
  211. if (binder.writeBeanIfValid(edited)) {
  212. refresh(edited);
  213. eventRouter.fireEvent(new EditorSaveEvent<>(this, edited));
  214. return true;
  215. }
  216. }
  217. return false;
  218. }
  219. @Override
  220. public boolean isOpen() {
  221. return edited != null;
  222. }
  223. @Override
  224. public void cancel() {
  225. doCancel(false);
  226. rpc.cancel();
  227. }
  228. @Override
  229. public void editRow(int rowNumber)
  230. throws IllegalStateException, IllegalArgumentException {
  231. if (!isEnabled()) {
  232. throw new IllegalStateException("Item editor is not enabled");
  233. }
  234. T beanToEdit = getParent().getDataCommunicator().
  235. fetchItemsWithRange(rowNumber, 1).
  236. stream().findFirst().orElseThrow(() -> new IllegalArgumentException(
  237. "Row number " + rowNumber+ "did not yield any item from data provider"));
  238. if (!beanToEdit.equals(edited)) {
  239. if (isBuffered() && edited != null) {
  240. throw new IllegalStateException("Editing item " + beanToEdit
  241. + " failed. Item editor is already editing item "
  242. + edited);
  243. } else {
  244. rpc.bind(rowNumber);
  245. }
  246. }
  247. }
  248. private void doCancel(boolean afterBeingSaved) {
  249. T editedBean = edited;
  250. doClose();
  251. if (!afterBeingSaved) {
  252. eventRouter.fireEvent(new EditorCancelEvent<>(this, editedBean));
  253. }
  254. }
  255. /**
  256. * Handles clean up for closing the Editor.
  257. */
  258. protected void doClose() {
  259. edited = null;
  260. for (Component c : columnFields.values()) {
  261. removeComponentFromGrid(c);
  262. }
  263. columnFields.clear();
  264. getState().columnFields.clear();
  265. }
  266. @Override
  267. public Editor<T> setSaveCaption(String saveCaption) {
  268. Objects.requireNonNull(saveCaption);
  269. getState().saveCaption = saveCaption;
  270. return this;
  271. }
  272. @Override
  273. public Editor<T> setCancelCaption(String cancelCaption) {
  274. Objects.requireNonNull(cancelCaption);
  275. getState().cancelCaption = cancelCaption;
  276. return this;
  277. }
  278. @Override
  279. public String getSaveCaption() {
  280. return getState(false).saveCaption;
  281. }
  282. @Override
  283. public String getCancelCaption() {
  284. return getState(false).cancelCaption;
  285. }
  286. @Override
  287. protected EditorState getState() {
  288. return getState(true);
  289. }
  290. @Override
  291. protected EditorState getState(boolean markAsDirty) {
  292. return (EditorState) super.getState(markAsDirty);
  293. }
  294. @Override
  295. public Editor<T> setErrorGenerator(EditorErrorGenerator<T> errorGenerator) {
  296. Objects.requireNonNull(errorGenerator, "Error generator can't be null");
  297. this.errorGenerator = errorGenerator;
  298. return this;
  299. }
  300. @Override
  301. public EditorErrorGenerator<T> getErrorGenerator() {
  302. return errorGenerator;
  303. }
  304. @Override
  305. public Registration addSaveListener(EditorSaveListener<T> listener) {
  306. return eventRouter.addListener(EditorSaveEvent.class, listener,
  307. ReflectTools.getMethod(EditorSaveListener.class));
  308. }
  309. @Override
  310. public Registration addCancelListener(EditorCancelListener<T> listener) {
  311. return eventRouter.addListener(EditorCancelEvent.class, listener,
  312. ReflectTools.getMethod(EditorCancelListener.class));
  313. }
  314. @Override
  315. public Registration addOpenListener(EditorOpenListener<T> listener) {
  316. return eventRouter.addListener(EditorOpenEvent.class, listener,
  317. ReflectTools.getMethod(EditorOpenListener.class));
  318. }
  319. @Override
  320. public Grid<T> getGrid() {
  321. return getParent();
  322. }
  323. }