From 7a16225c3b4fd6b94274fe0d9ffe00557b973f3c Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Mon, 2 Jun 2025 16:39:03 +0200 Subject: [PATCH 1/2] add custom component MonthYearPicker --- .../kontor/comics/views/IssueForm.java | 22 +---- .../kontor/common/views/MonthYearPicker.java | 95 +++++++++++++++++++ .../common/views/YearMonthConverter.java | 31 ++++++ 3 files changed, 130 insertions(+), 18 deletions(-) create mode 100644 kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java create mode 100644 kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java index a54d9ec..61a03af 100644 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java @@ -19,6 +19,7 @@ import com.vaadin.flow.data.binder.*; import com.vaadin.flow.data.converter.*; import de.thpeetz.kontor.comics.data.*; +import de.thpeetz.kontor.common.views.*; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -28,7 +29,7 @@ public class IssueForm extends FormLayout { ComboBox volume = new ComboBox<>("Volume"); TextField issueNumber = new TextField("Issue number"); TextField title = new TextField("Full Title"); - TextField publishedOn = new TextField("Published"); + MonthYearPicker publishedOn = new MonthYearPicker(); Checkbox isRead = new Checkbox("Read"); Checkbox inStock = new Checkbox("In stock"); @@ -40,23 +41,8 @@ public class IssueForm extends FormLayout { public IssueForm(List comics) { addClassName("issue-form"); - binder.forField(publishedOn).withConverter(new Converter() { - @Override - public Result convertToModel(String value, ValueContext context) { - try { - YearMonth result = YearMonth.parse(value); - return Result.ok(result); - } catch (DateTimeParseException e) { - return Result.error("invalid year-month format"); - } - } - - @Override - public String convertToPresentation(YearMonth value, ValueContext context) { - if (value == null) return ""; - return value.format(DateTimeFormatter.ofPattern("yyyy-MM")); - } - }).bind(Issue::getPublishedOn, Issue::setPublishedOn); + binder.forField(publishedOn).withConverter(new YearMonthConverter(publishedOn)). + bind(Issue::getPublishedOn, Issue::setPublishedOn); binder.bindInstanceFields(this); comic.setItems(comics); diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java new file mode 100644 index 0000000..b6312f7 --- /dev/null +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java @@ -0,0 +1,95 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.component.*; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.shared.Registration; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDate; +import java.time.Month; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.format.TextStyle; +import java.util.List; +import java.util.Locale; +import java.util.stream.IntStream; + +@Route("date-picker-individual-input-fields") +@Slf4j +public class MonthYearPicker extends Div implements HasValue, String> { + + private final ComboBox yearPicker; + private final ComboBox monthPicker; + + public MonthYearPicker() { + LocalDate now = LocalDate.now(ZoneId.systemDefault()); + List selectableYears = IntStream.range(1970, now.getYear()).boxed().toList(); + + yearPicker = new ComboBox<>("Jahr", selectableYears); + yearPicker.setWidth(6, Unit.EM); + + monthPicker = new ComboBox<>("Monat", Month.values()); + monthPicker.setItemLabelGenerator(m -> m.getDisplayName(TextStyle.FULL, Locale.getDefault())); + monthPicker.setWidth(9, Unit.EM); + + add(new HorizontalLayout(yearPicker, monthPicker)); + } + + @Override + public void setValue(String value) { + log.info("MonthYearPicker.setValue({})", value); + } + + public void setValue(YearMonth value) { + log.info("MonthYearPicker.setValue({})", value.toString()); + } + + @Override + public String getValue() { + if (this.getYearMonth() != null) { + return this.getYearMonth().toString(); + } + return null; + } + + public YearMonth getYearMonth() { + if (yearPicker.getValue() != null && monthPicker.getValue() != null) { + int year = yearPicker.getValue(); + int month = monthPicker.getValue().getValue(); + YearMonth result = YearMonth.of(year, month); + log.info("MonthYearPicker.getYearMonth({})", result); + return result; + } else { + log.info("MonthYearPicker.getYearMonth(null)"); + return null; + } + } + + @Override + public Registration addValueChangeListener(ValueChangeListener> listener) { + return null; + } + + @Override + public void setReadOnly(boolean readOnly) { + + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { + + } + + @Override + public boolean isRequiredIndicatorVisible() { + return false; + } +} diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java new file mode 100644 index 0000000..3f0abf9 --- /dev/null +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java @@ -0,0 +1,31 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.data.binder.Result; +import com.vaadin.flow.data.binder.ValueContext; +import com.vaadin.flow.data.converter.Converter; + +import java.time.*; +import java.time.format.*; + +public class YearMonthConverter implements Converter { + private MonthYearPicker monthYearPicker; + + public YearMonthConverter(MonthYearPicker monthYearPicker) { + this.monthYearPicker = monthYearPicker; + } + + @Override + public Result convertToModel(String value, ValueContext context) { + try { + YearMonth result = this.monthYearPicker.getYearMonth(); + return Result.ok(result); + } catch (DateTimeParseException e) { + return Result.error("invalid year-month format"); + } + } + + @Override + public String convertToPresentation(YearMonth value, ValueContext context) { + return monthYearPicker.getValue(); + } +} -- 2.18.0 From f1f49ab014dc49593dab23f3a681bb9d10eddbdc Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Mon, 2 Jun 2025 22:30:28 +0200 Subject: [PATCH 2/2] use CustomField to create combined year and month field --- .../kontor/comics/views/IssueForm.java | 15 ++- .../kontor/common/views/MonthYearPicker.java | 95 ------------------- .../common/views/YearMonthConverter.java | 31 ------ .../kontor/common/views/YearMonthField.java | 54 +++++++++++ 4 files changed, 60 insertions(+), 135 deletions(-) delete mode 100644 kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java delete mode 100644 kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java create mode 100644 kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthField.java diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java index 61a03af..d580e2b 100644 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java @@ -1,7 +1,5 @@ package de.thpeetz.kontor.comics.views; -import java.time.*; -import java.time.format.*; import java.util.List; import com.vaadin.flow.component.ComponentEvent; @@ -11,14 +9,15 @@ import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.Checkbox; import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.datepicker.*; import com.vaadin.flow.component.formlayout.FormLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.binder.*; -import com.vaadin.flow.data.converter.*; -import de.thpeetz.kontor.comics.data.*; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.Issue; +import de.thpeetz.kontor.comics.data.Volume; import de.thpeetz.kontor.common.views.*; import lombok.extern.slf4j.Slf4j; @@ -29,7 +28,7 @@ public class IssueForm extends FormLayout { ComboBox volume = new ComboBox<>("Volume"); TextField issueNumber = new TextField("Issue number"); TextField title = new TextField("Full Title"); - MonthYearPicker publishedOn = new MonthYearPicker(); + YearMonthField publishedOn = new YearMonthField(); Checkbox isRead = new Checkbox("Read"); Checkbox inStock = new Checkbox("In stock"); @@ -41,8 +40,6 @@ public class IssueForm extends FormLayout { public IssueForm(List comics) { addClassName("issue-form"); - binder.forField(publishedOn).withConverter(new YearMonthConverter(publishedOn)). - bind(Issue::getPublishedOn, Issue::setPublishedOn); binder.bindInstanceFields(this); comic.setItems(comics); diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java deleted file mode 100644 index b6312f7..0000000 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/MonthYearPicker.java +++ /dev/null @@ -1,95 +0,0 @@ -package de.thpeetz.kontor.common.views; - -import com.vaadin.flow.component.*; -import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; -import com.vaadin.flow.router.Route; -import com.vaadin.flow.shared.Registration; -import lombok.extern.slf4j.Slf4j; - -import java.time.LocalDate; -import java.time.Month; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.format.TextStyle; -import java.util.List; -import java.util.Locale; -import java.util.stream.IntStream; - -@Route("date-picker-individual-input-fields") -@Slf4j -public class MonthYearPicker extends Div implements HasValue, String> { - - private final ComboBox yearPicker; - private final ComboBox monthPicker; - - public MonthYearPicker() { - LocalDate now = LocalDate.now(ZoneId.systemDefault()); - List selectableYears = IntStream.range(1970, now.getYear()).boxed().toList(); - - yearPicker = new ComboBox<>("Jahr", selectableYears); - yearPicker.setWidth(6, Unit.EM); - - monthPicker = new ComboBox<>("Monat", Month.values()); - monthPicker.setItemLabelGenerator(m -> m.getDisplayName(TextStyle.FULL, Locale.getDefault())); - monthPicker.setWidth(9, Unit.EM); - - add(new HorizontalLayout(yearPicker, monthPicker)); - } - - @Override - public void setValue(String value) { - log.info("MonthYearPicker.setValue({})", value); - } - - public void setValue(YearMonth value) { - log.info("MonthYearPicker.setValue({})", value.toString()); - } - - @Override - public String getValue() { - if (this.getYearMonth() != null) { - return this.getYearMonth().toString(); - } - return null; - } - - public YearMonth getYearMonth() { - if (yearPicker.getValue() != null && monthPicker.getValue() != null) { - int year = yearPicker.getValue(); - int month = monthPicker.getValue().getValue(); - YearMonth result = YearMonth.of(year, month); - log.info("MonthYearPicker.getYearMonth({})", result); - return result; - } else { - log.info("MonthYearPicker.getYearMonth(null)"); - return null; - } - } - - @Override - public Registration addValueChangeListener(ValueChangeListener> listener) { - return null; - } - - @Override - public void setReadOnly(boolean readOnly) { - - } - - @Override - public boolean isReadOnly() { - return false; - } - - @Override - public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { - - } - - @Override - public boolean isRequiredIndicatorVisible() { - return false; - } -} diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java deleted file mode 100644 index 3f0abf9..0000000 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthConverter.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.thpeetz.kontor.common.views; - -import com.vaadin.flow.data.binder.Result; -import com.vaadin.flow.data.binder.ValueContext; -import com.vaadin.flow.data.converter.Converter; - -import java.time.*; -import java.time.format.*; - -public class YearMonthConverter implements Converter { - private MonthYearPicker monthYearPicker; - - public YearMonthConverter(MonthYearPicker monthYearPicker) { - this.monthYearPicker = monthYearPicker; - } - - @Override - public Result convertToModel(String value, ValueContext context) { - try { - YearMonth result = this.monthYearPicker.getYearMonth(); - return Result.ok(result); - } catch (DateTimeParseException e) { - return Result.error("invalid year-month format"); - } - } - - @Override - public String convertToPresentation(YearMonth value, ValueContext context) { - return monthYearPicker.getValue(); - } -} diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthField.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthField.java new file mode 100644 index 0000000..edc228a --- /dev/null +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/YearMonthField.java @@ -0,0 +1,54 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.customfield.CustomField; +import com.vaadin.flow.component.select.Select; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDate; +import java.time.Month; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.format.TextStyle; +import java.util.List; +import java.util.Locale; +import java.util.stream.IntStream; + +@Slf4j +public class YearMonthField extends CustomField { + + private final Select year = new Select<>();; + private final Select month = new Select<>(); + + public YearMonthField() { + LocalDate now = LocalDate.now(ZoneId.systemDefault()); + List selectableYears = IntStream.range(1970, now.getYear()).boxed().toList(); + year.setItems(selectableYears); + year.setWidth(6, Unit.EM); + month.setItems(Month.values()); + month.setItemLabelGenerator(m -> m.getDisplayName(TextStyle.FULL, Locale.getDefault())); + month.setWidth(9, Unit.EM); + add(year, month); + } + + @Override + protected YearMonth generateModelValue() { + if (year.getValue() != null && month.getValue() != null) { + int yearValue = year.getValue(); + int monthValue = month.getValue().getValue(); + YearMonth result = YearMonth.of(yearValue, monthValue); + log.debug("YearMonthField.generateModelValue() = {}", result); + return result; + } else { + log.debug("YearMonthField.generateModelValue() = null"); + return null; + } + } + + @Override + protected void setPresentationValue(YearMonth yearMonth) { + if (yearMonth == null) return; + year.setValue(yearMonth.getYear()); + month.setValue(yearMonth.getMonth()); + } +} -- 2.18.0