Vaadin binder throw an exception on empty field - binding

In my Vaadin (v.23.2.6) application I have a form tied up to Filter class which has 5 attributes.
All of them are optional, i.e. user can leave the blank.
public FilterPanel(ApiBookUtils api) {
this.api = api;
this.authorField = new ComboBox<Author>("Author Name");
this.countryField = new ComboBox<>("Country");
this.countryField.setReadOnly(true);
this.fromYear = new IntegerField ("From");
this.fromYear.setWidth("60px");
this.toYear = new IntegerField ("To");
this.toYear.setWidth("60px");
this.binder = new Binder(Filter.class);
this.setModal(true);
this.setCloseOnOutsideClick(false);
this.setCloseOnEsc(true);
buildDialog();
}
private void buildDialog() {
bindFields();
addFields();
setDialogListeners();
setDialogItems();
}
private void bindFields() {
this.binder.bind(authorField, Filter::getAuthor, Filter::setAuthor);
this.binder.forField(countryField).bind(Filter::getCountry, Filter::setCountry);
this.binder.forField(fromYear).bind(Filter::getFromYear, Filter::setFromYear);
this.binder.forField(toYear).bind(Filter::getToYear, Filter::setToYear);
this.binder.forField(postingDateField).bind(Filter::getPostingDate, Filter::setPostingDate);
this.binder.forField(tagField).bind(Filter::getTags, Filter::setTags);
}
I am getting getting exception if IntegerField is left blank.
com.vaadin.flow.data.binder.BindingException: An exception has been thrown inside binding logic for the field element [label='From']
at com.vaadin.flow.data.binder.Binder$BindingImpl.execute(Binder.java:1570) ~[flow-data-23.2.5.jar:23.2.5]
at com.vaadin.flow.data.binder.Binder$BindingImpl.writeFieldValue(Binder.java:1427) ~[flow-data-23.2.5.jar:23.2.5]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Caused by: java.lang.NullPointerException: null
at com.vaadin.flow.data.binder.Binder$BindingImpl.lambda$writeFieldValue$5169480d$1(Binder.java:1431) ~[flow-data-23.2.5.jar:23.2.5]
Does anybody know how to make binder to accept empty field and set up default value in the bean?

I found the workaround the bug in the Binder. Apparently, it does not process primitive types correctly. I have replaced int fields in my bean with Integer object and exception was gone.

If IntegerField is empty, the value is by definition null. If your business logic throws NPE due this, Binder will fail. You can set the binding to convert null value to your presentation by using withNullPresentation(defaultValue).
this.binder.forField(fromYear)
.withNullPresentation(0) // e.g. interpret null as zero
.bind(Filter::getFromYear, Filter::setFromYear);

Related

Vaadin data Binder - ComboBox issues

Later Edit: I noticed that by returning one of the options in ValueProvider's apply method leads to having the check mark present, but appears to show the previous select too. I.e. if the current and previous values are distinct, two check marks are shown.
I am having troubles with ComboBox binding. I cannot get the com.vaadin.flow.data.binder.Binder properly select an option inside the combobox - i.e. tick the check mark in the dropdown.
My binder is a "generic", i.e. I am using it along with a Map, and I provide dynamic getters/setters for various map keys. So, consider Binder<Map>, while one of the properites inside the Map should be holding a Person's id.
ComboBox<Person> combobox = new ComboBox<>("Person");
List<Person> options = fetchPersons();
combobox.setItems(options);
combobox.setItemLabelGenerator(new ItemLabelGenerator<Person>() {
#Override
public String apply(final Person p) {
return p.getName();
}
});
binder.bind(combobox, new ValueProvider<Map, Person>() {
#Override
public Person apply(final Map p) {
return new Person((Long)p.get("id"), (String)p.get("name"));
}
}, new Setter<Map, Person>() {
#Override
public void accept(final Map bean, final Person p) {
bean.put("name", p.getName());
}
});
Wondering what could I possibly do wrong...
Later edit: Adding a screenshot for the Status ComboBox which has a String for caption and Integer for value.
Your problem is that you are creating a new instance in your binding, which is not working. You probably have some other bean, (I say here Bean) where Person is a property. So you want to use Binder of type Bean, to bind ComboBox to the property, which is a Person. And then populate your form with the Bean by using e.g. binder.readBean(bean). Btw. using Java 8 syntax makes your code much less verbose.
Bean bean = fetchBean();
Binder<Bean> binder = new Binder();
ComboBox<Person> combobox = new ComboBox<>("Person");
List<Person> options = fetchPersons();
combobox.setItems(options);
combobox.setItemLabelGenerator(Person::getName);
binder.forField(combobox).bind(Bean::getPerson, Bean::setPerson);
binder.readBean(bean);

Glass Mapper "Unrecognized Guid format" error on MapPropertiesToObject

I'm getting an error from GlassMapper on certain pages in my Sitecore solution.
public override void Execute(ObjectConstructionArgs args)
{
// check that no other task has created an object and that this is a dynamic object
if (args.Result != null || args.Configuration.Type.IsAssignableFrom(typeof(IDynamicMetaObjectProvider))) return;
// create instance using your container
var obj = ServiceLocator.ServiceProvider.GetService(args.Configuration.Type);
// map properties from item to model
args.Configuration.MapPropertiesToObject(obj, args.Service, args.AbstractTypeCreationContext);
// set the new object as the returned result
args.Result = obj;
}
The error is occurring on args.Configuration.MapPropertiesToObject(obj, args.Service, args.AbstractTypeCreationContext); - System.FormatException: Unrecognized Guid format.. This error is only occurring on pages of a particular template, but not ALL pages of that template and I'm not sure how to track down which field is causing the guid error
I experienced the same error when a General Link field didn't contain any id="{xxx}" attribute or had it empty like id="". So, I suggest to check the raw value of the link fields on the failing pages.

What would be the best way to check whether all fields are valid?

I have fields in a Window, some with validators and all bound to properties.
The validation works as expected.
But -
I do not want to proceed when any field is invalid. What would be the best way to determine if any validation went wrong?
There are several ways of dealing with validation in Vaadin, all supported by Vaadin (no need for custom boolean afterValidationFlag).
One possible way (preffered by me) shown below:
public class CustomWindow extends Window {
DateField customBeanFirstPropertyName = new DateField("Caption1");
ComboBox customBeanSecondPropertyName = new ComboBox("Caption2");
TextArea customBeanThirdPropertyName = new TextArea("Caption3");
BeanFieldGroup<CustomBean> binder = new BeanFieldGroup<>(CustomBean.class);
public CustomWindow(CustomBean customBean) {
buildLayout();
binder.buildAndBindMemberFields(this);
binder.setItemDataSource(new BeanItem<>(customBean));
//add validators
customBeanFirstPropertyName.addValidator((Validator) value -> {
if (value == null) throw new Validator.InvalidValueException("nonnull required");
});
customBeanThirdPropertyName.addValidator(
new RegexpValidator(".{3,20}", "length between 3-20 required")
);
/*
or have basic validators on #Entity level with e.g. javax.validation.constraints.Size
example:
#Size(min = 3, max = 20)
#Column(name = "customBeanThirdPropertyName", unique = true)
private String customBeanThirdPropertyName;
*/
}
void commit(Button.ClickEvent event) { //method called by "save" button
try {
binder.commit(); //internally calls valid() method on each field, which could throw exception
CustomBean customBeanAfterValidation = binder.getItemDataSource().getBean(); //custom actions with validated bean from binder
this.close();
} catch (FieldGroup.CommitException e) {
Map<Field<?>, Validator.InvalidValueException> invalidFields = e.getInvalidFields(); //do sth with invalid fields
}
}
}
If you use a FieldGroup instance to bind your fields with the properties, which is the recommended way, you can write:
fieldGroup.isValid();
That checks on all field validations of the fields managed by the field group.
Maintain a flag. Before proceeding, check if the flag is set. In the validation code, set the flag if the validation fails.

How can I return additional (i.e. more than just field=>error message) validation information from custom validation code to the Controller or View?

I am looking for a way to return the following information from my custom validation code:
public enum ValidationErrorTypeFlags
{
Error_Input = 1 << 0, // a "field specific" error which is checked both clientside and serverside
Error_Input_ServerSide = 1 << 1, // a "field specific" error which can only be checked serverside
Error_General = 1 << 2 // serverside general error
}
Inside the validation code (either an IValidatableObject or a ValidationAttribute), when I detect an error, I would like to be able to associate one of the above error types with the ValidationResult.
Then I want to be able to iterate through the validation errors in either the Controller or the View and distinguish between these error types.
I'm currently using MVC 3 (happy to upgrade to 4).
NB:
ModelState does not preserve ValidationResults AFAIK - you can only access errors in ViewData.ModelState.Values.Items[x].Errors - and these have been converted to System.Web.Mvc.ModelError
It seems that MVC validation only allows you to access [key, 'error message'] type validation results after validation has completed.
The hack I'm using at present is to decorate the error message inside the custom validation code:
var field = new[] { validationContext.DisplayName };
return new ValidationResult("+Invalid format - use yyyy-mm-dd", field);
And then look for error messages which start with +,-,* in the controller.
From custom validation code (no idea how to accomplish from built-in ones) you can do that by creating a custom ValidationResult class by inheriting from the base and return from your custom validation attributes.
public class CustomValidationResult: ValidationResult
{
// additional properties
}
Then from the controller you can cast and check if the validation result is your custom type and act accordingly.
Update:
The above idea don't work because the ValidationResult class is in DataAnnotations assembly and they are converted into ModelValidationResult and that's all we can access in MVC.
It seems passing extra information from the data annotation validations to the MVC looks like not quite easy!
I was going through the source code and found that it is the ValidatableObjectAdapter that converts the IEnumerable<ValidationResult> into IEnumerable<ModelValidationResult>. I don't see much benefit on extending this class but we can easily create a custom ValidatableObjectAdapter by implementing the ModelValidatorand duplicating the Validate code.
We have to create a custom ModelValidationResult and custom ValidationResult(it is this custom ValidationResult we will b returning from validations) and in the ConvertResults method we can put our conversion code that takes care of the additional information.
public class CustomValidatableObjectAdapter : ModelValidator
{
public CustomValidatableObjectAdapter(ModelMetadata metadata, ControllerContext context)
: base(metadata, context)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
object model = Metadata.Model;
if (model == null)
{
return Enumerable.Empty<ModelValidationResult>();
}
IValidatableObject validatable = model as IValidatableObject;
if (validatable == null)
{
throw new Exception("model is of not type validatable");
}
ValidationContext validationContext = new ValidationContext(validatable, null, null);
return ConvertResults(validatable.Validate(validationContext));
}
private IEnumerable<ModelValidationResult> ConvertResults(IEnumerable<ValidationResult> results)
{
foreach (ValidationResult result in results)
{
// iterate the ValidationResult enumeration and cast each into CustomValidationResult
// and conver them into enumeration of CustomModelValidationResult.
}
}
}
Finally we have to tell the DataAnnotationsModelValidatorProvider use this our CustomValidatableObjectAdapter in the Application_Start event of Global.asax.cs.
DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapterFactory((metadata, context) => new CustomValidatableObjectAdapter(metadata, context));
So you have to create a custom ValidationResult, custom ModelValidationResult and a custom ValidatableObjectAdapter.
I haven't tested this but I hope this will work. I may suggest a better and easier solution than this.

asp.net MVC 3 Error: "Object reference not set to an instance of an object"

i've a AccountController like this
public class AccountController : Controller
{
[Authorize]
public ActionResult MyProfile(string userEmail)
{
UserManager um = new UserManager();
UserProfile user = new UserProfile();
user = um.GetUserDetail(userEmail);
return View(user);
}
}
i've UserManager.cs Like this
public class UserManager
{
private ToLetDBEntities TLE = new ToLetDBEntities();
public UserProfile GetUserDetail(string uemail)
{
var userDetails = TLE.users.FirstOrDefault(x => x.email_add == uemail);
UserProfile up = new UserProfile();
up.cellno = userDetails.cellno.Trim();
up.email_add = userDetails.email_add.Trim();
up.name = userDetails.name.Trim();
up.password = userDetails.password.Trim();
return up;
}
}
When i'm debugging it gives error like
Object reference not set to an instance of an object
Null Reference Exception was Unhandled by User
At the line
up.cellno=userDetails.cellno.Trim();
Of the GetUserDetails function.
That error suggests that you don't have a userDetails instance, so you can't get the cellno property.
Are you sure that TLE.users.FirstOrDefault(x => x.email_add == uemail) is returning something? If you put a breakpoint on the line that gives you the error, then you can check what the value of userDetails is - it's probably null.
Most likely this query isn't returning anything:
TLE.users.FirstOrDefault(x => x.email_add == uemail);
The FirstOrDefault method won't give any kind of indication if no record is returned. The OrDefault part specifies this behavior. For any given return type, it will return the "default" for that type if no record is found.
For reference types (which yours is), the default is null. So this call would result in exactly that exception:
userDetails.cellno.Trim();
Since userDetails is null then it can't access a cellno property, hence the exception.
It's also possible in your case that userDetails isn't null but that the cellno property on it is. It's less likely, but possible. Looking at the runtime value while debugging would tell you if that's the case.
If userDetails is null, check your query conditions. Maybe the users collection has nothing in it? Maybe there are no records which match your x.email_add == uemail condition? (This is likely.)
If cellno is null then you'll want to check how that object is built, what the data in the database looks like, etc. Either way, you're not getting back the data you expect. The issue is in the data you're accessing.

Resources