Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods to modify an existing binding #5030

Closed
heruan opened this issue Feb 7, 2019 · 7 comments · Fixed by #8094
Closed

Add methods to modify an existing binding #5030

heruan opened this issue Feb 7, 2019 · 7 comments · Fixed by #8094

Comments

@heruan
Copy link
Member

heruan commented Feb 7, 2019

Given an existing binding, it's currently not possible to modify its properties; for example, given

var binding = binder.forField(field).bind(getter, setter);

I might need to add additional validation, like:

  • binding.setRequired(true)
  • binding.addValidator(validator)

depending on user actions or status of other fields.

@berndklb
Copy link

Hi @heruan
is this change https://github.com/vaadin/flow/compare/master...berndklb:feature/5030_change_existing_binding?expand=1 something you would expect? At the moment I implemented only the setRequired and addValidator methods.

@heruan
Copy link
Member Author

heruan commented Mar 28, 2019

@berndklb I have to test it yet, but it seems like it!

@heruan
Copy link
Member Author

heruan commented Oct 17, 2019

Any update on this?

@heruan
Copy link
Member Author

heruan commented Oct 18, 2019

@berndklb Can't find your branch anymore, is it still available?

@berndklb
Copy link

I cleaned up a while ago, but I repushed the branch.
But I did not investigate further into this so far.

@heruan
Copy link
Member Author

heruan commented Oct 19, 2019

Thank you @berndklb for pushing that again. In the meanwhile, I've crafted a custom Binder as a workaround:

public class CustomBinder<T> extends Binder<T> {

    public interface CustomBinding<T, V> extends Binding<T, V> {

        default void setRequired(boolean required) {
            if (required) {
                this.setRequired(context -> "");
            } else {
                this.setRequired(Validator.alwaysPass());
            }
            this.getField().setRequiredIndicatorVisible(required);
            this.validate(false);
        }

        void setRequired(ErrorMessageProvider errorMessageProvider);

        void setRequired(Validator<V> validator);

    }

    public interface CustomBindingBuilder<T, V> extends BindingBuilder<T, V> {

        @Override
        default CustomBindingBuilder<T, V> asRequired() {
            return this.asRequired(context -> "");
        }

        @Override
        default CustomBindingBuilder<T, V> asRequired(String errorMessage) {
            return this.asRequired(context -> errorMessage);
        }

        @Override
        CustomBindingBuilder<T, V> asRequired(ErrorMessageProvider errorMessageProvider);

        @Override
        CustomBindingBuilder<T, V> asRequired(Validator<V> customRequiredValidator);

        @Override
        CustomBinding<T, V> bind(ValueProvider<T, V> getter, Setter<T, V> setter);

    }

    protected static class CustomValidatorImpl<T> implements Validator<T> {

        private Validator<T> upstream;

        public CustomValidatorImpl(Validator<T> upstream) {
            this.upstream = upstream;
        }

        @Override
        public ValidationResult apply(T value, ValueContext context) {
            return this.upstream.apply(value, context);
        }

    }

    protected static class CustomBindingBuilderImpl<T, U, V> extends BindingBuilderImpl<T, U, V>
            implements CustomBindingBuilder<T, V> {

        private final HasValue<?, U> field;

        private final Converter<U, V> converterValidatorChain;

        private CustomValidatorImpl<V> requiredValidator;

        protected CustomBindingBuilderImpl(Binder<T> binder, HasValue<?, U> field,
                Converter<U, V> converterValidatorChain,
                BindingValidationStatusHandler statusHandler) {
            super(binder, field, converterValidatorChain, statusHandler);
            this.field = field;
            this.converterValidatorChain = converterValidatorChain;
        }

        @Override
        public CustomBindingBuilder<T, V> withValidator(Validator<? super V> validator) {
            super.withValidator(validator);
            return this;
        }

        @Override
        public CustomBindingBuilder<T, V> asRequired(ErrorMessageProvider errorMessageProvider) {
            return this.asRequired((v, c) -> Validator.<V>from(value -> {
                var emptyValue = this.field.getEmptyValue();
                var convertedValue = this.converterValidatorChain.convertToPresentation(value, c);
                return !Objects.equals(convertedValue, emptyValue);
            }, errorMessageProvider).apply(v, c));
        }

        @Override
        public CustomBindingBuilder<T, V> asRequired(Validator<V> customRequiredValidator) {
            this.requiredValidator = new CustomValidatorImpl<>(customRequiredValidator);
            this.checkUnbound();
            this.field.setRequiredIndicatorVisible(true);
            return this.withValidator(this.requiredValidator);
        }

        @Override
        public CustomBinding<T, V> bind(ValueProvider<T, V> getter, Setter<T, V> setter) {
            var upstream = super.bind(getter, setter);
            return new CustomBindingImpl<>(upstream, this);
        }

    }

    protected static class CustomBindingImpl<T, U, V> implements CustomBinding<T, V> {

        private final Binding<T, V> upstream;

        private final HasValue<?, U> field;

        private final Converter<U, V> converterValidatorChain;

        private final CustomValidatorImpl<V> requiredValidator;

        public CustomBindingImpl(Binding<T, V> upstream,
                CustomBindingBuilderImpl<T, U, V> builder) {
            this.upstream = upstream;
            this.field = builder.field;
            this.converterValidatorChain = builder.converterValidatorChain;
            this.requiredValidator = builder.requiredValidator;
        }

        @Override
        public void setRequired(ErrorMessageProvider errorMessageProvider) {
            this.setRequired((v, c) -> Validator.<V>from(value -> {
                var emptyValue = this.field.getEmptyValue();
                var convertedValue = this.converterValidatorChain.convertToPresentation(value, c);
                return !Objects.equals(convertedValue, emptyValue);
            }, errorMessageProvider).apply(v, c));
        }

        @Override
        public void setRequired(Validator<V> validator) {
            this.requiredValidator.upstream = validator;
        }

        @Override
        public HasValue<?, ?> getField() {
            return this.upstream.getField();
        }

        @Override
        public BindingValidationStatus<V> validate(boolean fireEvent) {
            return this.upstream.validate(fireEvent);
        }

        @Override
        public BindingValidationStatusHandler getValidationStatusHandler() {
            return this.upstream.getValidationStatusHandler();
        }

        @Override
        public void unbind() {
            this.upstream.unbind();
        }

        @Override
        public void read(T bean) {
            this.upstream.read(bean);
        }

        @Override
        public void setReadOnly(boolean readOnly) {
            this.upstream.setReadOnly(readOnly);
        }

        @Override
        public boolean isReadOnly() {
            return this.upstream.isReadOnly();
        }

        @Override
        public ValueProvider<T, V> getGetter() {
            return this.upstream.getGetter();
        }

        @Override
        public Setter<T, V> getSetter() {
            return this.upstream.getSetter();
        }

    }

    @Override
    public <U> CustomBindingBuilder<T, U> forField(HasValue<?, U> field) {
        return (CustomBindingBuilder<T, U>) super.forField(field);
    }

    @Override
    protected <U, V> BindingBuilder<T, V> doCreateBinding(HasValue<?, U> field,
            Converter<U, V> converter, BindingValidationStatusHandler handler) {
        return new CustomBindingBuilderImpl<>(this, field, converter, handler);
    }

}

But I still think the feature should be on core, so I hope to get rid of this soon 🙂

@pleku
Copy link
Contributor

pleku commented Oct 28, 2019

Acceptance Criteria

  • Inclusion of the API as suggested in couple comments above
  • Unit tests that verify that the API does what it should (no ITs should be required)

This feature is up for grabs for contributing, please read the contribution guidelines https://github.com/vaadin/flow/blob/master/CONTRIBUTING.md and ask any questions in here or eg. https://gitter.im/vaadin/flow if necessary.

TatuLund added a commit that referenced this issue Dec 27, 2019
It is a very common use case in complex form that whether a field is required or not, it depends on input on other fields. Hypothetical use case sample could be that we have form for a Product and price of the product is needed except in case the Product's type is Sample. So in that kind of scenarios it would be needed to turn off asRequired() validation easily. The purpose of this enhancement and new binding.setAsRequiredEnabled(..) API is to help implementation of this kind of use cases more easily.

There is more generic ticket about conditional validation, and this PR is partially addressing it #10709

Cherry pick from vaadin/framework#11834

Requested in #5030
denis-anisimov pushed a commit that referenced this issue Jan 13, 2020
It is a very common use case in complex form that whether a field is required or not, it depends on input on other fields. Hypothetical use case sample could be that we have form for a Product and price of the product is needed except in case the Product's type is Sample. So in that kind of scenarios it would be needed to turn off asRequired() validation easily. The purpose of this enhancement and new binding.setAsRequiredEnabled(..) API is to help implementation of this kind of use cases more easily.

There is more generic ticket about conditional validation, and this PR is partially addressing it #10709

Cherry pick from vaadin/framework#11834

Requested in #5030
TatuLund added a commit that referenced this issue Apr 17, 2020
- Enable / disable all validators on Binder level
- Enable / disable validators on Binding level
- add writeBeanAsDraft(bean,boolean) for writing draft bean with validators disabled

Fixes: #5030
caalador pushed a commit that referenced this issue May 6, 2020
- Enable / disable all validators on Binder level
- Enable / disable validators on Binding level
- add writeBeanAsDraft(bean,boolean) for writing draft bean with validators disabled

Fixes #5030
denis-anisimov pushed a commit that referenced this issue May 8, 2020
- Enable / disable all validators on Binder level
- Enable / disable validators on Binding level
- add writeBeanAsDraft(bean,boolean) for writing draft bean with validators disabled

Fixes #5030
pleku pushed a commit that referenced this issue May 11, 2020
- Enable / disable all validators on Binder level
- Enable / disable validators on Binding level
- add writeBeanAsDraft(bean,boolean) for writing draft bean with validators disabled

Fixes #5030
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants