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

BinderBookOfVaadinTest.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  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.data;
  17. import java.time.LocalDate;
  18. import java.util.List;
  19. import java.util.concurrent.atomic.AtomicBoolean;
  20. import java.util.concurrent.atomic.AtomicReference;
  21. import java.util.stream.Collectors;
  22. import org.junit.Assert;
  23. import org.junit.Before;
  24. import org.junit.Test;
  25. import com.vaadin.data.Binder.Binding;
  26. import com.vaadin.data.Binder.BindingBuilder;
  27. import com.vaadin.data.BindingValidationStatus.Status;
  28. import com.vaadin.data.converter.StringToIntegerConverter;
  29. import com.vaadin.data.validator.EmailValidator;
  30. import com.vaadin.data.validator.StringLengthValidator;
  31. import com.vaadin.server.AbstractErrorMessage;
  32. import com.vaadin.ui.Button;
  33. import com.vaadin.ui.DateField;
  34. import com.vaadin.ui.Label;
  35. import com.vaadin.ui.Notification;
  36. import com.vaadin.ui.Slider;
  37. import com.vaadin.ui.TextField;
  38. /**
  39. * Book of Vaadin tests.
  40. *
  41. * @author Vaadin Ltd
  42. *
  43. */
  44. public class BinderBookOfVaadinTest {
  45. private static class BookPerson {
  46. private String lastName;
  47. private String email, phone, title;
  48. private int yearOfBirth, salaryLevel;
  49. public BookPerson(int yearOfBirth, int salaryLevel) {
  50. this.yearOfBirth = yearOfBirth;
  51. this.salaryLevel = salaryLevel;
  52. }
  53. public BookPerson(BookPerson origin) {
  54. this(origin.yearOfBirth, origin.salaryLevel);
  55. lastName = origin.lastName;
  56. email = origin.email;
  57. phone = origin.phone;
  58. title = origin.title;
  59. }
  60. public String getLastName() {
  61. return lastName;
  62. }
  63. public void setLastName(String lastName) {
  64. this.lastName = lastName;
  65. }
  66. public int getYearOfBirth() {
  67. return yearOfBirth;
  68. }
  69. public void setYearOfBirth(int yearOfBirth) {
  70. this.yearOfBirth = yearOfBirth;
  71. }
  72. public int getSalaryLevel() {
  73. return salaryLevel;
  74. }
  75. public void setSalaryLevel(int salaryLevel) {
  76. this.salaryLevel = salaryLevel;
  77. }
  78. public String getEmail() {
  79. return email;
  80. }
  81. public void setEmail(String email) {
  82. this.email = email;
  83. }
  84. public String getPhone() {
  85. return phone;
  86. }
  87. public void setPhone(String phone) {
  88. this.phone = phone;
  89. }
  90. public String getTitle() {
  91. return title;
  92. }
  93. public void setTitle(String title) {
  94. this.title = title;
  95. }
  96. }
  97. public static class Trip {
  98. private LocalDate returnDate;
  99. public LocalDate getReturnDate() {
  100. return returnDate;
  101. }
  102. public void setReturnDate(LocalDate returnDate) {
  103. this.returnDate = returnDate;
  104. }
  105. }
  106. private Binder<BookPerson> binder;
  107. private TextField field;
  108. private TextField phoneField;
  109. private TextField emailField;
  110. @Before
  111. public void setUp() {
  112. binder = new Binder<>();
  113. field = new TextField();
  114. phoneField = new TextField();
  115. emailField = new TextField();
  116. }
  117. @Test
  118. public void loadingFromBusinessObjects() {
  119. // this test is just to make sure the code snippet in the book compiles
  120. binder.readBean(new BookPerson(1969, 50000));
  121. BinderValidationStatus<BookPerson> status = binder.validate();
  122. if (status.hasErrors()) {
  123. Notification.show("Validation error count: "
  124. + status.getValidationErrors().size());
  125. }
  126. }
  127. @Test
  128. public void handlingCheckedException() {
  129. // another test just to verify that book examples actually compile
  130. try {
  131. binder.writeBean(new BookPerson(2000, 50000));
  132. } catch (ValidationException e) {
  133. Notification.show("Validation error count: "
  134. + e.getValidationErrors().size());
  135. }
  136. }
  137. @Test
  138. public void simpleEmailValidator() {
  139. binder.forField(field)
  140. // Explicit validator instance
  141. .withValidator(new EmailValidator(
  142. "This doesn't look like a valid email address"))
  143. .bind(BookPerson::getEmail, BookPerson::setEmail);
  144. field.setValue("not-email");
  145. BinderValidationStatus<?> status = binder.validate();
  146. Assert.assertEquals(1, status.getFieldValidationErrors().size());
  147. Assert.assertEquals("This doesn't look like a valid email address",
  148. status.getFieldValidationErrors().get(0).getMessage().get());
  149. Assert.assertEquals("This doesn't look like a valid email address",
  150. ((AbstractErrorMessage) field.getErrorMessage()).getMessage());
  151. field.setValue("abc@vaadin.com");
  152. status = binder.validate();
  153. Assert.assertEquals(0, status.getBeanValidationErrors().size());
  154. Assert.assertNull(field.getErrorMessage());
  155. }
  156. @Test
  157. public void nameLengthTest() {
  158. binder.forField(field)
  159. // Validator defined based on a lambda and an error message
  160. .withValidator(name -> name.length() >= 3,
  161. "Last name must contain at least three characters")
  162. .bind(BookPerson::getLastName, BookPerson::setLastName);
  163. field.setValue("a");
  164. BinderValidationStatus<?> status = binder.validate();
  165. Assert.assertEquals(1, status.getFieldValidationErrors().size());
  166. Assert.assertEquals("Last name must contain at least three characters",
  167. status.getFieldValidationErrors().get(0).getMessage().get());
  168. Assert.assertEquals("Last name must contain at least three characters",
  169. ((AbstractErrorMessage) field.getErrorMessage()).getMessage());
  170. field.setValue("long last name");
  171. status = binder.validate();
  172. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  173. Assert.assertNull(field.getErrorMessage());
  174. }
  175. @Test
  176. public void chainedEmailValidator() {
  177. binder.forField(field)
  178. // Explicit validator instance
  179. .withValidator(new EmailValidator(
  180. "This doesn't look like a valid email address"))
  181. .withValidator(email -> email.endsWith("@acme.com"),
  182. "Only acme.com email addresses are allowed")
  183. .bind(BookPerson::getEmail, BookPerson::setEmail);
  184. field.setValue("not-email");
  185. BinderValidationStatus<?> status = binder.validate();
  186. // Only one error per field should be reported
  187. Assert.assertEquals(1, status.getFieldValidationErrors().size());
  188. Assert.assertEquals("This doesn't look like a valid email address",
  189. status.getFieldValidationErrors().get(0).getMessage().get());
  190. Assert.assertEquals("This doesn't look like a valid email address",
  191. ((AbstractErrorMessage) field.getErrorMessage()).getMessage());
  192. field.setValue("abc@vaadin.com");
  193. status = binder.validate();
  194. Assert.assertEquals(1, status.getFieldValidationErrors().size());
  195. Assert.assertEquals("Only acme.com email addresses are allowed",
  196. status.getFieldValidationErrors().get(0).getMessage().get());
  197. Assert.assertEquals("Only acme.com email addresses are allowed",
  198. ((AbstractErrorMessage) field.getErrorMessage()).getMessage());
  199. field.setValue("abc@acme.com");
  200. status = binder.validate();
  201. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  202. Assert.assertNull(field.getErrorMessage());
  203. }
  204. @Test
  205. public void converterBookOfVaadinExample1() {
  206. TextField yearOfBirthField = new TextField();
  207. // Slider for integers between 1 and 10
  208. Slider salaryLevelField = new Slider("Salary level", 1, 10);
  209. BindingBuilder<BookPerson, String> b1 = binder
  210. .forField(yearOfBirthField);
  211. BindingBuilder<BookPerson, Integer> b2 = b1.withConverter(
  212. new StringToIntegerConverter("Must enter a number"));
  213. b2.bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
  214. BindingBuilder<BookPerson, Double> salaryBinding1 = binder
  215. .forField(salaryLevelField);
  216. BindingBuilder<BookPerson, Integer> salaryBinding2 = salaryBinding1
  217. .withConverter(Double::intValue, Integer::doubleValue);
  218. salaryBinding2.bind(BookPerson::getSalaryLevel,
  219. BookPerson::setSalaryLevel);
  220. // Test that the book code works
  221. BookPerson bookPerson = new BookPerson(1972, 4);
  222. binder.setBean(bookPerson);
  223. Assert.assertEquals(4.0, salaryLevelField.getValue().doubleValue(), 0);
  224. Assert.assertEquals("1,972", yearOfBirthField.getValue());
  225. bookPerson.setSalaryLevel(8);
  226. binder.readBean(bookPerson);
  227. Assert.assertEquals(8.0, salaryLevelField.getValue().doubleValue(), 0);
  228. bookPerson.setYearOfBirth(123);
  229. binder.readBean(bookPerson);
  230. Assert.assertEquals("123", yearOfBirthField.getValue());
  231. yearOfBirthField.setValue("2016");
  232. salaryLevelField.setValue(1.0);
  233. Assert.assertEquals(2016, bookPerson.getYearOfBirth());
  234. Assert.assertEquals(1, bookPerson.getSalaryLevel());
  235. }
  236. @Test
  237. public void converterBookOfVaadinExample2() {
  238. TextField yearOfBirthField = new TextField();
  239. binder.forField(yearOfBirthField)
  240. .withConverter(Integer::valueOf, String::valueOf,
  241. // Text to use instead of the NumberFormatException
  242. // message
  243. "Please enter a number")
  244. .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
  245. binder.setBean(new BookPerson(1900, 5));
  246. yearOfBirthField.setValue("abc");
  247. binder.validate();
  248. Assert.assertEquals("Please&#32;enter&#32;a&#32;number",
  249. yearOfBirthField.getComponentError().getFormattedHtmlMessage());
  250. }
  251. @Test
  252. public void crossFieldValidation_validateUsingBinder() {
  253. Binder<Trip> binder = new Binder<>();
  254. DateField departing = new DateField("Departing");
  255. DateField returning = new DateField("Returning");
  256. Binding<Trip, LocalDate> returnBinding = binder.forField(returning)
  257. .withValidator(
  258. returnDate -> !returnDate
  259. .isBefore(departing.getValue()),
  260. "Cannot return before departing")
  261. .bind(Trip::getReturnDate, Trip::setReturnDate);
  262. departing.addValueChangeListener(event -> returnBinding.validate());
  263. LocalDate past = LocalDate.now();
  264. LocalDate before = past.plusDays(1);
  265. LocalDate after = before.plusDays(1);
  266. departing.setValue(before);
  267. returning.setValue(after);
  268. BinderValidationStatus<Trip> status = binder.validate();
  269. Assert.assertTrue(status.getBeanValidationErrors().isEmpty());
  270. Assert.assertNull(departing.getComponentError());
  271. Assert.assertNull(returning.getComponentError());
  272. // update returning => validation is done against this field
  273. returning.setValue(past);
  274. status = binder.validate();
  275. Assert.assertFalse(status.getFieldValidationErrors().isEmpty());
  276. Assert.assertNotNull(returning.getComponentError());
  277. Assert.assertNull(departing.getComponentError());
  278. // set correct value back
  279. returning.setValue(before);
  280. status = binder.validate();
  281. Assert.assertTrue(status.getFieldValidationErrors().isEmpty());
  282. Assert.assertNull(departing.getComponentError());
  283. Assert.assertNull(returning.getComponentError());
  284. // update departing => validation is done because of listener added
  285. departing.setValue(after);
  286. status = binder.validate();
  287. Assert.assertFalse(status.getFieldValidationErrors().isEmpty());
  288. Assert.assertNotNull(returning.getComponentError());
  289. Assert.assertNull(departing.getComponentError());
  290. }
  291. @Test
  292. public void crossFieldValidation_validateUsingBinding() {
  293. Binder<Trip> binder = new Binder<>();
  294. DateField departing = new DateField("Departing");
  295. DateField returning = new DateField("Returning");
  296. Binding<Trip, LocalDate> returnBinding = binder.forField(returning)
  297. .withValidator(
  298. returnDate -> !returnDate
  299. .isBefore(departing.getValue()),
  300. "Cannot return before departing")
  301. .bind(Trip::getReturnDate, Trip::setReturnDate);
  302. departing.addValueChangeListener(event -> returnBinding.validate());
  303. LocalDate past = LocalDate.now();
  304. LocalDate before = past.plusDays(1);
  305. LocalDate after = before.plusDays(1);
  306. departing.setValue(before);
  307. returning.setValue(after);
  308. BindingValidationStatus<LocalDate> result = returnBinding.validate();
  309. Assert.assertFalse(result.isError());
  310. Assert.assertNull(departing.getComponentError());
  311. // update returning => validation is done against this field
  312. returning.setValue(past);
  313. result = returnBinding.validate();
  314. Assert.assertTrue(result.isError());
  315. Assert.assertNotNull(returning.getComponentError());
  316. // set correct value back
  317. returning.setValue(before);
  318. result = returnBinding.validate();
  319. Assert.assertFalse(result.isError());
  320. Assert.assertNull(departing.getComponentError());
  321. // update departing => validation is done because of listener added
  322. departing.setValue(after);
  323. result = returnBinding.validate();
  324. Assert.assertTrue(result.isError());
  325. Assert.assertNotNull(returning.getComponentError());
  326. }
  327. @Test
  328. public void withStatusLabelExample() {
  329. Label emailStatus = new Label();
  330. String msg = "This doesn't look like a valid email address";
  331. binder.forField(field).withValidator(new EmailValidator(msg))
  332. .withStatusLabel(emailStatus)
  333. .bind(BookPerson::getEmail, BookPerson::setEmail);
  334. field.setValue("foo");
  335. binder.validate();
  336. Assert.assertTrue(emailStatus.isVisible());
  337. Assert.assertEquals(msg, emailStatus.getValue());
  338. field.setValue("foo@vaadin.com");
  339. binder.validate();
  340. Assert.assertFalse(emailStatus.isVisible());
  341. Assert.assertEquals("", emailStatus.getValue());
  342. }
  343. @Test
  344. public void withBindingStatusHandlerExample() {
  345. Label nameStatus = new Label();
  346. AtomicReference<BindingValidationStatus<?>> statusCapture = new AtomicReference<>();
  347. String msg = "Full name must contain at least three characters";
  348. binder.forField(field).withValidator(name -> name.length() >= 3, msg)
  349. .withValidationStatusHandler(status -> {
  350. nameStatus.setValue(status.getMessage().orElse(""));
  351. // Only show the label when validation has failed
  352. boolean error = status.getStatus() == Status.ERROR;
  353. nameStatus.setVisible(error);
  354. statusCapture.set(status);
  355. }).bind(BookPerson::getLastName, BookPerson::setLastName);
  356. field.setValue("aa");
  357. binder.validate();
  358. Assert.assertTrue(nameStatus.isVisible());
  359. Assert.assertEquals(msg, nameStatus.getValue());
  360. Assert.assertNotNull(statusCapture.get());
  361. BindingValidationStatus<?> status = statusCapture.get();
  362. Assert.assertEquals(Status.ERROR, status.getStatus());
  363. Assert.assertEquals(msg, status.getMessage().get());
  364. Assert.assertEquals(field, status.getField());
  365. field.setValue("foo");
  366. binder.validate();
  367. Assert.assertFalse(nameStatus.isVisible());
  368. Assert.assertEquals("", nameStatus.getValue());
  369. Assert.assertNotNull(statusCapture.get());
  370. status = statusCapture.get();
  371. Assert.assertEquals(Status.OK, status.getStatus());
  372. Assert.assertFalse(status.getMessage().isPresent());
  373. Assert.assertEquals(field, status.getField());
  374. }
  375. @Test
  376. public void binder_saveIfValid() {
  377. Binder<BookPerson> binder = new Binder<>(BookPerson.class);
  378. // Phone or email has to be specified for the bean
  379. Validator<BookPerson> phoneOrEmail = Validator.from(
  380. personBean -> !"".equals(personBean.getPhone())
  381. || !"".equals(personBean.getEmail()),
  382. "A person must have either a phone number or an email address");
  383. binder.withValidator(phoneOrEmail);
  384. binder.forField(emailField).bind("email");
  385. binder.forField(phoneField).bind("phone");
  386. // Person person = // e.g. JPA entity or bean from Grid
  387. BookPerson person = new BookPerson(1900, 5);
  388. person.setEmail("Old Email");
  389. // Load person data to a form
  390. binder.readBean(person);
  391. Button saveButton = new Button("Save", event -> {
  392. // Using saveIfValid to avoid the try-catch block that is
  393. // needed if using the regular save method
  394. if (binder.writeBeanIfValid(person)) {
  395. // Person is valid and updated
  396. // TODO Store in the database
  397. }
  398. });
  399. emailField.setValue("foo@bar.com");
  400. Assert.assertTrue(binder.writeBeanIfValid(person));
  401. // Person updated
  402. Assert.assertEquals("foo@bar.com", person.getEmail());
  403. emailField.setValue("");
  404. Assert.assertFalse(binder.writeBeanIfValid(person));
  405. // Person updated because phone and email are both empty
  406. Assert.assertEquals("foo@bar.com", person.getEmail());
  407. }
  408. @Test
  409. public void manyConvertersAndValidators() throws ValidationException {
  410. TextField yearOfBirthField = new TextField();
  411. binder.forField(yearOfBirthField)
  412. // Validator will be run with the String value of the field
  413. .withValidator(text -> text.length() == 4,
  414. "Doesn't look like a year")
  415. // Converter will only be run for strings with 4 characters
  416. .withConverter(
  417. new StringToIntegerConverter("Must enter a number"))
  418. // Validator will be run with the converted value
  419. .withValidator(year -> year >= 1900 && year <= 2000,
  420. "Person must be born in the 20th century")
  421. .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
  422. yearOfBirthField.setValue("abc");
  423. Assert.assertEquals("Doesn't look like a year", binder.validate()
  424. .getFieldValidationErrors().get(0).getMessage().get());
  425. yearOfBirthField.setValue("abcd");
  426. Assert.assertEquals("Must enter a number", binder.validate()
  427. .getFieldValidationErrors().get(0).getMessage().get());
  428. yearOfBirthField.setValue("1200");
  429. Assert.assertEquals("Person must be born in the 20th century",
  430. binder.validate().getFieldValidationErrors().get(0).getMessage()
  431. .get());
  432. yearOfBirthField.setValue("1950");
  433. Assert.assertFalse(binder.validate().hasErrors());
  434. BookPerson person = new BookPerson(1500, 12);
  435. binder.writeBean(person);
  436. Assert.assertEquals(1950, person.getYearOfBirth());
  437. }
  438. class MyConverter implements Converter<String, Integer> {
  439. @Override
  440. public Result<Integer> convertToModel(String fieldValue,
  441. ValueContext context) {
  442. // Produces a converted value or an error
  443. try {
  444. // ok is a static helper method that creates a Result
  445. return Result.ok(Integer.valueOf(fieldValue));
  446. } catch (NumberFormatException e) {
  447. // error is a static helper method that creates a Result
  448. return Result.error("Please enter a number");
  449. }
  450. }
  451. @Override
  452. public String convertToPresentation(Integer integer,
  453. ValueContext context) {
  454. // Converting to the field type should always succeed,
  455. // so there is no support for returning an error Result.
  456. return String.valueOf(integer);
  457. }
  458. }
  459. @Test
  460. public void bindUsingCustomConverter() {
  461. Binder<BookPerson> binder = new Binder<>();
  462. TextField yearOfBirthField = new TextField();
  463. // Using the converter
  464. binder.forField(yearOfBirthField).withConverter(new MyConverter())
  465. .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
  466. BookPerson p = new BookPerson(1500, 12);
  467. binder.setBean(p);
  468. yearOfBirthField.setValue("abc");
  469. Assert.assertTrue(binder.validate().hasErrors());
  470. Assert.assertEquals("Please enter a number", binder.validate()
  471. .getFieldValidationErrors().get(0).getMessage().get());
  472. yearOfBirthField.setValue("123");
  473. Assert.assertTrue(binder.validate().isOk());
  474. p.setYearOfBirth(12500);
  475. binder.readBean(p);
  476. Assert.assertEquals("12500", yearOfBirthField.getValue());
  477. Assert.assertTrue(binder.validate().isOk());
  478. }
  479. @Test
  480. public void withBinderStatusLabelExample() {
  481. Label formStatusLabel = new Label();
  482. Binder<BookPerson> binder = new Binder<>(BookPerson.class);
  483. binder.setStatusLabel(formStatusLabel);
  484. final String message = "Too young, son";
  485. final String message2 = "Y2K error";
  486. TextField yearOfBirth = new TextField();
  487. BookPerson p = new BookPerson(1500, 12);
  488. binder.forField(yearOfBirth)
  489. .withConverter(new StringToIntegerConverter("err"))
  490. .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
  491. binder.withValidator(bean -> bean.yearOfBirth < 2000, message)
  492. .withValidator(bean -> bean.yearOfBirth != 2000, message2);
  493. binder.setBean(p);
  494. // first bean validator fails and passes error message to status label
  495. yearOfBirth.setValue("2001");
  496. BinderValidationStatus<?> status = binder.validate();
  497. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  498. Assert.assertEquals(1, status.getBeanValidationErrors().size());
  499. Assert.assertEquals(message,
  500. status.getBeanValidationErrors().get(0).getErrorMessage());
  501. Assert.assertEquals(message, formStatusLabel.getValue());
  502. // value is correct, status label is cleared
  503. yearOfBirth.setValue("1999");
  504. status = binder.validate();
  505. Assert.assertFalse(status.hasErrors());
  506. Assert.assertEquals("", formStatusLabel.getValue());
  507. // both bean validators fail, should be two error messages chained
  508. yearOfBirth.setValue("2000");
  509. status = binder.validate();
  510. Assert.assertEquals(2, status.getBeanValidationResults().size());
  511. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  512. Assert.assertEquals(2, status.getBeanValidationErrors().size());
  513. // only first error is shown
  514. Assert.assertEquals(message, formStatusLabel.getValue());
  515. }
  516. @Test
  517. public void withBinderStatusHandlerExample() {
  518. Label formStatusLabel = new Label();
  519. BinderValidationStatusHandler<BookPerson> defaultHandler = binder
  520. .getValidationStatusHandler();
  521. binder.setValidationStatusHandler(status -> {
  522. // create an error message on failed bean level validations
  523. List<ValidationResult> errors = status.getBeanValidationErrors();
  524. String errorMessage = errors.stream()
  525. .map(ValidationResult::getErrorMessage)
  526. .collect(Collectors.joining("\n"));
  527. // show error in a label
  528. formStatusLabel.setValue(errorMessage);
  529. formStatusLabel.setVisible(!errorMessage.isEmpty());
  530. // Let the default handler show messages for each field
  531. defaultHandler.statusChange(status);
  532. });
  533. final String bindingMessage = "uneven";
  534. final String message = "Too young, son";
  535. final String message2 = "Y2K error";
  536. TextField yearOfBirth = new TextField();
  537. BookPerson p = new BookPerson(1500, 12);
  538. binder.forField(yearOfBirth)
  539. .withConverter(new StringToIntegerConverter("err"))
  540. .withValidator(value -> value % 2 == 0, bindingMessage)
  541. .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
  542. binder.withValidator(bean -> bean.yearOfBirth < 2000, message)
  543. .withValidator(bean -> bean.yearOfBirth != 2000, message2);
  544. binder.setBean(p);
  545. // first binding validation fails, no bean level validation is done
  546. yearOfBirth.setValue("2001");
  547. BinderValidationStatus<?> status = binder.validate();
  548. Assert.assertEquals(1, status.getFieldValidationErrors().size());
  549. Assert.assertEquals(bindingMessage,
  550. status.getFieldValidationErrors().get(0).getMessage().get());
  551. Assert.assertEquals("", formStatusLabel.getValue());
  552. // first bean validator fails and passes error message to status label
  553. yearOfBirth.setValue("2002");
  554. status = binder.validate();
  555. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  556. Assert.assertEquals(1, status.getBeanValidationErrors().size());
  557. Assert.assertEquals(message,
  558. status.getBeanValidationErrors().get(0).getErrorMessage());
  559. Assert.assertEquals(message, formStatusLabel.getValue());
  560. // value is correct, status label is cleared
  561. yearOfBirth.setValue("1998");
  562. status = binder.validate();
  563. Assert.assertTrue(status.isOk());
  564. Assert.assertFalse(status.hasErrors());
  565. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  566. Assert.assertEquals(0, status.getBeanValidationErrors().size());
  567. Assert.assertEquals("", formStatusLabel.getValue());
  568. // both bean validators fail, should be two error messages chained
  569. yearOfBirth.setValue("2000");
  570. status = binder.validate();
  571. Assert.assertEquals(0, status.getFieldValidationErrors().size());
  572. Assert.assertEquals(2, status.getBeanValidationErrors().size());
  573. Assert.assertEquals(message + "\n" + message2,
  574. formStatusLabel.getValue());
  575. }
  576. @Test
  577. public void statusChangeListener_binderIsNotBound() {
  578. Button saveButton = new Button();
  579. Button resetButton = new Button();
  580. AtomicBoolean eventIsFired = new AtomicBoolean(false);
  581. binder.addStatusChangeListener(event -> {
  582. boolean isValid = !event.hasValidationErrors();
  583. boolean hasChanges = event.getBinder().hasChanges();
  584. eventIsFired.set(true);
  585. saveButton.setEnabled(hasChanges && isValid);
  586. resetButton.setEnabled(hasChanges);
  587. });
  588. binder.forField(field)
  589. .withValidator(new StringLengthValidator("", 1, 3))
  590. .bind(BookPerson::getLastName, BookPerson::setLastName);
  591. // no changes
  592. Assert.assertFalse(saveButton.isEnabled());
  593. Assert.assertFalse(resetButton.isEnabled());
  594. verifyEventIsFired(eventIsFired);
  595. BookPerson person = new BookPerson(2000, 1);
  596. binder.readBean(person);
  597. // no changes
  598. Assert.assertFalse(saveButton.isEnabled());
  599. Assert.assertFalse(resetButton.isEnabled());
  600. verifyEventIsFired(eventIsFired);
  601. field.setValue("a");
  602. // binder is not bound, no event fired
  603. // no changes: see #375. There should be a change and enabled state
  604. Assert.assertTrue(saveButton.isEnabled());
  605. Assert.assertTrue(resetButton.isEnabled());
  606. Assert.assertTrue(eventIsFired.get());
  607. binder.writeBeanIfValid(person);
  608. // no changes
  609. Assert.assertFalse(saveButton.isEnabled());
  610. Assert.assertFalse(resetButton.isEnabled());
  611. verifyEventIsFired(eventIsFired);
  612. binder.validate();
  613. // no changes
  614. Assert.assertFalse(saveButton.isEnabled());
  615. Assert.assertFalse(resetButton.isEnabled());
  616. verifyEventIsFired(eventIsFired);
  617. field.setValue("");
  618. // binder is not bound, no event fired
  619. // no changes: see #375. There should be a change and disabled state for
  620. // save button because of failed validation
  621. Assert.assertFalse(saveButton.isEnabled());
  622. Assert.assertTrue(resetButton.isEnabled());
  623. Assert.assertTrue(eventIsFired.get());
  624. }
  625. @Test
  626. public void statusChangeListener_binderIsBound() {
  627. Button saveButton = new Button();
  628. Button resetButton = new Button();
  629. AtomicBoolean eventIsFired = new AtomicBoolean(false);
  630. binder.addStatusChangeListener(event -> {
  631. boolean isValid = !event.hasValidationErrors();
  632. boolean hasChanges = event.getBinder().hasChanges();
  633. eventIsFired.set(true);
  634. saveButton.setEnabled(hasChanges && isValid);
  635. resetButton.setEnabled(hasChanges);
  636. });
  637. binder.forField(field)
  638. .withValidator(new StringLengthValidator("", 1, 3))
  639. .bind(BookPerson::getLastName, BookPerson::setLastName);
  640. // no changes
  641. Assert.assertFalse(saveButton.isEnabled());
  642. Assert.assertFalse(resetButton.isEnabled());
  643. verifyEventIsFired(eventIsFired);
  644. BookPerson person = new BookPerson(2000, 1);
  645. binder.setBean(person);
  646. // no changes
  647. Assert.assertFalse(saveButton.isEnabled());
  648. Assert.assertFalse(resetButton.isEnabled());
  649. verifyEventIsFired(eventIsFired);
  650. field.setValue("a");
  651. // there are valid changes
  652. verifyEventIsFired(eventIsFired);
  653. field.setValue("");
  654. // there are invalid changes
  655. Assert.assertFalse(saveButton.isEnabled());
  656. Assert.assertTrue(resetButton.isEnabled());
  657. verifyEventIsFired(eventIsFired);
  658. // set valid value
  659. field.setValue("a");
  660. verifyEventIsFired(eventIsFired);
  661. binder.writeBeanIfValid(person);
  662. // there are no changes.
  663. Assert.assertFalse(saveButton.isEnabled());
  664. Assert.assertFalse(resetButton.isEnabled());
  665. verifyEventIsFired(eventIsFired);
  666. }
  667. private void verifyEventIsFired(AtomicBoolean flag) {
  668. Assert.assertTrue(flag.get());
  669. flag.set(false);
  670. }
  671. }