3.5.1.7. Screen Validation

The ScreenValidation bean can be used to run validation in screens. It has the following methods:

  • ValidationErrors validateUiComponents() is used by default when committing changes in the StandardEditor, InputDialog, and MasterDetailScreen. The method accepts a collection of components or a component container and returns validation errors in these components (ValidationErrors object). The validateUiComponents() method also can be used in an arbitrary screen. For example:

    @UiController("demo_DemoScreen")
    @UiDescriptor("demo-screen.xml")
    public class DemoScreen extends Screen {
        @Inject
        private ScreenValidation screenValidation;
        @Inject
        private Form demoForm;
    
        @Subscribe("validateBtn")
        public void onValidateBtnClick(Button.ClickEvent event) {
            ValidationErrors errors = screenValidation.validateUiComponents(demoForm);
            if (!errors.isEmpty()) {
                screenValidation.showValidationErrors(this, errors);
                return;
            }
        }
    }
  • showValidationErrors() - displays a notification with all errors and problematic components. The method accepts the screen and ValidationErrors object. It is also used by default in the StandardEditor, InputDialog, and MasterDetailScreen.

  • validateCrossFieldRules() - accepts a screen and an entity and returns the ValidationErrors object. Performs cross-field validation rules. Editor screens validate class-level constraints on the commit if the constraints include the UiCrossFieldChecks group and all attribute-level constraint checks are successful (see more information in the Custom Constraints section). You can disable this type of validation using the setCrossFieldValidate() method of the controller. By default, it is used in the StandardEditor, MasterDetailScreen, in the editor of the DataGrid. The validateCrossFieldRules() method also can be used in an arbitrary screen.

    As an example, let’s look at the Event entity for which we can define a class-level annotation @EventDate to check that the Start date must be lower than the End date.

    Event entity
    @Table(name = "DEMO_EVENT")
    @Entity(name = "demo_Event")
    @NamePattern("%s|name")
    @EventDate(groups = {Default.class, UiCrossFieldChecks.class})
    public class Event extends StandardEntity {
        private static final long serialVersionUID = 1477125422077150455L;
    
        @Column(name = "NAME")
        private String name;
    
        @Temporal(TemporalType.TIMESTAMP)
        @Column(name = "START_DATE")
        private Date startDate;
    
        @Temporal(TemporalType.TIMESTAMP)
        @Column(name = "END_DATE")
        private Date endDate;
    
        ...
    }

    The annotation definition looks like this:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = EventDateValidator.class)
    public @interface EventDate {
    
        String message() default "The Start date must be earlier than the End date";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    EventDateValidator
    public class EventDateValidator implements ConstraintValidator<EventDate, Event> {
        @Override
        public boolean isValid(Event event, ConstraintValidatorContext context) {
            if (event == null) {
                return false;
            }
    
            if (event.getStartDate() == null || event.getEndDate() == null) {
                return false;
            }
    
            return event.getStartDate().before(event.getEndDate());
        }
    }

    Then you can use the validateCrossFieldRules() method in an arbitrary screen.

    @UiController("demo_DemoScreen")
    @UiDescriptor("demo-screen.xml")
    public class DemoScreen extends Screen {
    
        @Inject
        protected Metadata metadata;
        @Inject
        protected ScreenValidation screenValidation;
        @Inject
        protected TimeSource timeSource;
    
        @Subscribe("validateBtn")
        public void onValidateBtnClick(Button.ClickEvent event) {
            Event event = metadata.create(Event.class);
            event.setName("Demo event");
            event.setStartDate(timeSource.currentTimestamp());
    
            // We make the endDate earlier than the startDate
            event.setEndDate(DateUtils.addDays(event.getStartDate(), -1));
    
            ValidationErrors errors = screenValidation.validateCrossFieldRules(this, event);
            if (!errors.isEmpty()) {
                screenValidation.showValidationErrors(this, errors);
            }
        }
    }
  • showUnsavedChangesDialog() - shows the standard dialog for unsaved changes ("Do you want to discard unsaved changes?") with the Yes and No buttons. It is used in the StandardEditor. The showUnsavedChangesDialog() method has a handler that responds to user actions (the button that was clicked):

    screenValidation.showUnsavedChangesDialog(this, action)
                            .onDiscard(() -> result.resume(closeWithDiscard()))
                            .onCancel(result::fail);
  • showSaveConfirmationDialog() - shows the standard dialog for confirming saving changed data ("Do you want to save changes before close?") with the Save, Do not save, Cancel buttons. It is used in the StandardEditor. The showSaveConfirmationDialog() method has a handler that responds to user actions (the button that was clicked):

    screenValidation.showSaveConfirmationDialog(this, action)
                        .onCommit(() -> result.resume(closeWithCommit()))
                        .onDiscard(() -> result.resume(closeWithDiscard()))
                        .onCancel(result::fail);

You can adjust the dialog type using the cuba.gui.useSaveConfirmation application property.