CustomField valueChangeListener behavior

I need to use a CustomField to group info about a tracking event (code of delivery as Delivered, Not Delivered, etc… + delivery datetime).

In the route where I put my component (in a grid, a component for every row) I add a change listener but I have two issue:

  1. the listener only listen for the ComboBox changes;
  2. after listen correctly the first change, the listener seems to stop itself and I couldn’t see the System.out output more.

I think the issue is linked to override changeListener, but I could not figure out how to resolve it.

Code in route.

...
grid.addColumn(new ComponentRenderer<TrackingCodeDateTime, Shipment>(item -> {
		
			TrackingCodeDateTime field = new TrackingCodeDateTime(item.getTracking());
			field.setWidthFull();
		
			field.addValueChangeListener(event -> {
				System.out.println(event.getValue());
			});
		
			return field;
		
		})).setHeader("Delivery time");
...

CustomField code.

public class TrackingCodeDateTime extends CustomField<Tracking> {

	private final Tracking tracking;
	private final ComboBox<EventCode> eventCode;
	private final DatePicker datePicker;
	private final TimePicker timePicker;

	public TrackingCodeDateTime(Tracking tracking) {
		this.tracking = tracking;

		eventCode = new ComboBox<>();
		eventCode.setPlaceholder("Code");
		eventCode.setRequired(true);
		eventCode.setRequiredIndicatorVisible(true);
		// eventCode.setAllowCustomValue(false);
		eventCode.setWidth("14em");

		datePicker = new DatePicker();
		datePicker.setPlaceholder("Date");
		datePicker.setRequired(true);
		datePicker.setMin(minDate);
		datePicker.setWidth("9em");

		timePicker = new TimePicker();
		timePicker.setPlaceholder("Time");
		timePicker.setWidth("5em");

		if (tracking != null) {
			setPresentationValue(tracking);
		}

		FormLayout layout = new FormLayout();
		layout.add(eventCode, datePicker, timePicker);
		layout.setResponsiveSteps(new ResponsiveStep("14em", 1), new ResponsiveStep("9em", 2), new ResponsiveStep("5em", 3));
		add(layout);
	}

	@Override
	protected Tracking generateModelValue() {
		final LocalDate date = datePicker.getValue();
		final LocalTime time = timePicker.getValue();
		tracking.setEventCode(eventCode.getValue());
		tracking.setEventDateTime(date != null && time != null ? LocalDateTime.of(date, time) : null);
		return tracking;
	}

	@Override
	protected void setPresentationValue(Tracking newPresentationValue) {
		if (newPresentationValue == null) {
			eventCode.setValue(null);
			datePicker.setValue(null);
			timePicker.setValue(null);
		} else {
			LocalDateTime ldt = newPresentationValue.getEventDateTime();
			eventCode.setValue(newPresentationValue.getEventCode());
			datePicker.setValue(ldt != null ? ldt.toLocalDate() : null);
			timePicker.setValue(ldt != null ? ldt.toLocalTime() : null);
		}
	}

Pietro Ferraboschi:

Hi, did you solved the problem ? i have something similar.

Thanks

I have the same problem: Basically I am adding textfields in a grid and I want to add a key listener on each one of them. But it is never called and I do not understand why…

Here is my code:

for (Week w : weekRepository.findAll()) {

            Grid.Column<EmployeeProject> gridColumn = grid.addComponentColumn(employeeProject -> {
                TextField amountTime = new TextField();
                amountTime.setWidth("85px");
                amountTime.setValue(String.valueOf(employeeProject.getAmountTimeEmployeeOnProject(w)));

                amountTime.addKeyPressListener(Key.ENTER, keyPressEvent -> {
                    notificationMessage.openSuccess("Saving");
                    employeeProject.setAmountTimeEmployeeOnProject(employeeProject.getProject(), w, Integer.parseInt(amountTime.getValue()));
                    employeeProjectRepository.save(employeeProject);
                });

               return amountTime;
            });

            gridColumn.setHeader(w.getWeekNumber() + " / " + w.getYear());

        }

Has anyone solved this?

Hi Giovanni and hi Jaufray, here my last version. I’m pretty happy with it but not for component responsivness! I must open another ticket to solve it :wink:

public class TrackingEvent extends CustomField<Tracking> {

	// private static final long serialVersionUID = 4075248600075394610L;
	final private String ICON_SIZE = "16px";

	private final ComboBox<EventCode> code = new ComboBox<>(null);
	private final DatePicker date = new DatePicker();
	private final TimePicker time = new TimePicker();
	private final Button delete = new Button();
	private boolean complete = false;

	public TrackingEvent(EventCodeType eventCodeType, Set<EventCode> items) {
		this(new Tracking(eventCodeType), items, null, null);
	}

	public TrackingEvent(Tracking tracking, Set<EventCode> items) {
		this(tracking, items, null, null);
	}

	public TrackingEvent(Tracking tracking, Set<EventCode> items, LocalDate minDate, LocalTime minTime) {

		code.setItems(items);
		// code.setPlaceholder(getTranslation("placeholder.event_code"));
		code.setAllowCustomValue(false);
		code.setRequired(true);
		code.setWidthFull();
		// code.setWidth("13em");

		date.setMin(minDate);
		date.setMax(LocalDate.now());
		// date.setPlaceholder(getTranslation("placeholder.event_date"));
		date.setRequired(true);
		date.setWidth("9em");

		// time.setPlaceholder(getTranslation("placeholder.event_time"));
		if (minTime != null)
			time.setMin(minTime.format(DateTimeFormatter.ofPattern("HH:mm")));
		time.setWidth("7em");
		time.addValueChangeListener(event -> {
			// if time complete, the tracking is completed
			complete = event.getValue() != null;
			// updates the model value
			setModelValue(generateModelValue(), true);
		});

		// delete button, default is not visible
		delete.setVisible(false);
		delete.setEnabled(false);
		delete.setWidth(ICON_SIZE);
		delete.setIcon(VaadinIcon.TRASH.create());
		delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
		// show button's purpose "on hover"
		delete.getElement().setProperty("title", getTranslation("tooltip.delete_event"));

		Binder<Tracking> binder = new Binder<>();
		binder.bind(code, Tracking::getEventCode, Tracking::setEventCode);
		binder.bind(date, Tracking::getEventDate, Tracking::setEventDate);
		binder.bind(time, Tracking::getEventTime, Tracking::setEventTime);
		binder.addValueChangeListener(event -> enableDateTime());

		setPresentationValue(tracking);

		enableDateTime();

		HorizontalLayout layout = new HorizontalLayout();
		layout.setAlignItems(Alignment.CENTER);
		layout.setWidthFull();
		layout.add(code, date, time, delete);
		// layout.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
		// layout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.STRETCH);
		layout.setMargin(false);
		add(layout);

	}

	public void setTracking(Tracking tracking) {
		setPresentationValue(tracking);
		enableDateTime();
	}

	public void setMinDate(LocalDate minDate) {
		this.date.setMin(minDate);
	}

	public void setMinTime(LocalTime minTime) {
		this.time.setMin(minTime.format(DateTimeFormatter.ofPattern("HH:mm")));
	}

	@Override
	public void clear() {
		code.clear();
		date.clear();
		time.clear();
	}

	/*
	 * Se confermato, lo rendo immodificabile; altrimenti lascio tutto com'è.
	 * */
	public void setConfirmed(boolean confirmed) {
		if (confirmed) {
			code.setReadOnly(confirmed);
			date.setReadOnly(confirmed);
			time.setReadOnly(confirmed);
		}
	}

	private void enableDateTime() {
		if (code.getValue() == null) {
			date.clear();
			time.clear();
			date.setReadOnly(true);
			time.setReadOnly(true);
			delete.setEnabled(false);
		} else {
			date.setReadOnly(false);
			if (date.getValue() == null) {
				time.clear();
				time.setReadOnly(true);
				delete.setEnabled(false);
			} else {
				time.setReadOnly(false);
				delete.setEnabled(true);
			}
		}
	}

	public boolean isComplete() {
		return complete;
	}

	public void addDeleteButtonClickListener(ComponentEventListener<ClickEvent<Button>> listener) {
		delete.setVisible(true);
		delete.addClickListener(listener);
	}

	@Override
	protected Tracking generateModelValue() {
		Tracking tracking = new Tracking();
		tracking.setEventCode(code.getValue());
		tracking.setEventDate(date.getValue());
		tracking.setEventTime(time.getValue());
		return tracking;
	}

	@Override
	protected void setPresentationValue(Tracking newPresentationValue) {
		if (newPresentationValue == null) {
			clear();
		} else {
			code.setValue(newPresentationValue.getEventCode());
			date.setValue(newPresentationValue.getEventDate());
			// issue here https://github.com/vaadin/vaadin-time-picker-flow/issues/22
			time.setValue(newPresentationValue.getEventTime());
		}
	}

}

Pietro Ferraboschi:
Hi Giovanni and hi Jaufray, here my last version. I’m pretty happy with it but not for component responsivness! I must open another ticket to solve it :wink:

Hi, Pietro, thanks,

may be my problem was related to creation of new object instance in generateModelValue().

like you did.

@Override
protected Tracking generateModelValue() {
	Tracking tracking = new Tracking();
	tracking.setEventCode(code.getValue());
	tracking.setEventDate(date.getValue());
	tracking.setEventTime(time.getValue());
	return tracking;
}

Thanks.

Hi Pietro,

Thanks for your message. However, I don’t fully understand how you made it work inside your grid.addColumn…

I have tried to make a new TextFieldCustom extening TextField but I have the same problem: the keyEventListener is never called.

I works perfectly when it is outside my loop and grid.addComponentColumn but not inside.

And I don’t understand why

Jaufray Sornette:
Hi Pietro,

Thanks for your message. However, I don’t fully understand how you made it work inside your grid.addColumn…

I have tried to make a new TextFieldCustom extening TextField but I have the same problem: the keyEventListener is never called.

I works perfectly when it is outside my loop and grid.addComponentColumn but not inside.

And I don’t understand why

I think you have to bang your head on the wall of Binding :wink:

binder.addValueChangeListener … is the way to manage an event into a custom component.

Look for bind, binding or binder to have further information.