When I enter edit mode of my Table, I want to have data validation on all fields in that Table.
First, a couple of notes:
I'm using Vaadin 7, so the Bean Validation addon sadly won't work.
I know the implementation of JSR-303 works, because I tried adding a BeanValidator to a TextField without issues.
Now, I have a perfectly working table for which I am using a BeanItemContainer to keep my Person beans inside.
The Person bean looks as follows:
public class Person {
#Size(min = 5, max = 50)
private String firstName;
#Size(min = 5, max = 50)
private String lastName;
#Min(0)
#Max(2000)
private int description;
... getters + setters...
}
Person beans are added to the BeanItemContainer, which in turn is set to the container data source with setContainerDataSource()
The BeanValidator was added to the table like so:
table.addValidator(new BeanValidator(Person.class, "firstName"));
When I run the application, I have two problems:
When I run the application, the table shows up as intended. However, when I edit the fields and set one of the firstName fields to, say, "abc" - no validation error is shown and the value is accepted
How am I supposed to get BeanValidator to work on all of my tables fields?
When I use table.setSelectable(true) or table.setMultiSelect(true), I get this error:
com.vaadin.server.ServiceException:
java.lang.IllegalArgumentException: [] is not a valid value for
property firstName of type class
com.some.path.vaadinpoc.sampleapp.web.Person
How am I supposed to get BeanValidator to work with Selectable/MultiSelect?
Please advice
Thanks!
You'll need to add the validators to the editable fields themselves, not to the table. (Table itself is a field => the Validator from table.addValidator validates the value of the Table => the value of the table is the selected itemId(s) => the BeanValidator fails)
You can add the validators to the fields that by using a custom TableFieldFactory on the table. Here's a very simple one-off example for this scenario - clearly, if you need to do this with a lot of different beans/tables, it'll be worth creating a more generic/customizable factory
table.setTableFieldFactory(new DefaultFieldFactory() {
#Override
public Field<?> createField(Item item, Object propertyId, Component uiContext) {
Field<?> field = super.createField(item, propertyId, uiContext);
if (propertyId.equals("firstName")) {
field.addValidator(new BeanValidator(Person.class, "firstName"));
}
if (propertyId.equals("lastName")) {
field.addValidator(new BeanValidator(Person.class, "lastName"));
}
if (propertyId.equals("description")) {
field.addValidator(new BeanValidator(Person.class, "description"));
}
return field;
}
Related
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);
When I enter edit mode of my Table, I want the data validation exclamation mark icon (!) to be shown as soon as the user goes out of bounds of any of the validation constraints.
First, a couple of notes:
I'm using Vaadin 7, so the Bean Validation addon sadly won't work.
The data validation works as intended.
Now, I have a perfectly working table for which I am using a BeanItemContainer to keep my Person beans inside.
The code for the table and the TableFieldFactory looks something like this:
table.setContainerDataSource(buildContainer());
table.setTableFieldFactory(new TableFieldFactory() {
#Override
public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) {
TextField field = (TextField) DefaultFieldFactory.get().createField(container, itemId, propertyId,
uiContext);
field.setImmediate(true);
if (propertyId.equals("firstName")) {
field.addValidator(new BeanValidator(Person.class, "firstName"));
}
return field;
}
});
The Person bean looks as follows:
public class Person {
#Size(min = 5, max = 50)
private String firstName;
... setters + getters...
}
The problem is that when I type something in the firstName field and then press enter or blur/unfocus that field, no indication whatsoever of error is shown. I have to mouseover the field to see that something is wrong.
My question is two folded...
How do I get the exclamation mark icon to appear when the field is
invalid? (This works for a normal TextField that is not in a Table)
Is there a way to get an immediate response from the invalid field
(show the icon) (i.e. immediately after you type under 5 chars,
without having to press enter or blur/unfocus the field in
question).
Would be great if I could have both questions answered! =)
Thanks in advance!
The Caption, Required Indicator (the red asterisk) and - most importantly here - Error Indicator (exclamation mark) are actually provided by the layouts containing the component, not the component themselves. When editable components are displayed in a table, they are displayed without a layout - that's why no error indicator is displayed.
If I were trying to square this circle, I would look at creating a CustomField as a wrapper for the editable field - and within that CustomField display an error indicator when the wrapped/delegate field becomes invalid. I've not tried this - I've not used editable fields in a table at all - but should be fairly easy to do.
Add a TextChangeListener to the field in FieldFactory, and call field.validate() in the listener. Note, though, that field.getValue() value is not normally changed until blur/unfocus, ergo the validator will be validating the old value - unless you do field.setValue(event.getText()) in the listener. See this post on the Vaadin forum for more details.
This is the sort of thing I meant for a validating wrapper - not tried using it. You'll see initComponent simply returns the field inside a FormLayout, which should give you the icon(s) you're seeking. (You may need to delegate more methods from ValidatingWrapper to delegate than I have- but quick look suggests this may be enough.)
You'd then wrap the field in your tableFieldFactory (second code block)
public class ValidatingWrapper<T> extends CustomField<T> {
private static final long serialVersionUID = 9208404294767862319L;
protected Field<T> delegate;
public ValidatingWrapper(final Field<T> delegate) {
this.delegate = delegate;
if (delegate instanceof TextField) {
final TextField textField = (TextField) delegate;
textField.setTextChangeEventMode(AbstractTextField.TextChangeEventMode.TIMEOUT);
textField.setTextChangeTimeout(200);
textField.addTextChangeListener(new FieldEvents.TextChangeListener() {
#Override
public void textChange(FieldEvents.TextChangeEvent event) {
textField.setValue(event.getText());
textField.validate();
}
});
}
}
#Override
public Class<? extends T> getType() {
return delegate.getType();
}
#Override
protected Component initContent() {
return new FormLayout(delegate);
}
#Override
public Property getPropertyDataSource() {
return delegate.getPropertyDataSource();
}
#Override
public void setPropertyDataSource(Property newDataSource) {
delegate.setPropertyDataSource(newDataSource);
}
}
table.setContainerDataSource(buildContainer());
table.setTableFieldFactory(new TableFieldFactory() {
#Override
public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) {
TextField field = (TextField) DefaultFieldFactory.get().createField(container, itemId, propertyId,
uiContext);
field.setImmediate(true);
if (propertyId.equals("firstName")) {
field.addValidator(new BeanValidator(Person.class, "firstName"));
}
return ValidatingWrapper(field);
}
});
I am using Entity Framework 4.0, and making use of POCO objects. When I populate POCO objects from the DB, I translate property values to my own Domain objects, which we can call my Model.
Necessarily, whether or not the fields of my Model are Nullable depends on whether the value it maps to in the database comes from a NULL or NOT NULL column. I won't go into detail, but the values must be nullable in the DB, because a user can partially save a draft of the object before publishing it to the public. That being the case, I have several fields that are nullable. So let's say my model looks like:
public class MyModel
{
public int? Field1 {get; set; }
public DateTime? Field2 {get; set; }
public int Field3 {get; set; }
}
If I use this Model in my View, complete with nullable fields, I begin receiving errors that tell me I cannot use nullable properties as values in various places, like HTML helpers, etc. I could say something like if (Model.MyBoolField.HasValue && Model.MyBoolField.Value) { // etc }, but that feels bulky for a view.
I considered creating a ViewModel object that inherits from my original domain object and has new, non-nullable versions of my nullable fields that return an appropriate value if the base version is null. So something like:
public class MyViewModel : MyModel
{
public new int Field1
{
get { return base.Field1 ?? 7; }
}
public new DateTime Field2
{
get { return base.Field2 ?? DateTime.Now; }
}
}
My problem with this is that I don't always know a good "default" value to display. What if I threw an exception in the View Model's getter when the base value is null? Is that poor practice?
I'm basically looking for a best practice on how to handle nullable fields in a model, particularly when displaying in a View.
If you just need to display these fields in a View, you don't need to specify or check whether is has a value or not.
Using Model.Field1 in your View file is enough. It will simple not display anything, and it won't throw an exception. You can always use ?? to set a default when it makes sense.
#(Model.Field1 ?? "There is nothing to see here")
In most of the cases I use the "For" helpers, which seem OK with Nullable values (PublishedCount is a nullable property):
#Html.TextBoxFor(m => m.BillPull.PublishedCount, new { id="txtPublishedCount" })
#Html.ValidationMessageFor(m => m.BillPull.PublishedCount)
When I need to use just TextBox, I use the GetValueOrDefault method, with whatever default value the framework provides:
#Html.TextBox("BillPull.AutoPublishDate", Model.BillPull.AutoPublishDate.GetValueOrDefault().ToString(dateFormat), new { id = "dtpAutoPublishDate" })
#Html.ValidationMessageFor(m => m.BillPull.AutoPublishDate)
I got a simple POJO class that i wish to display / update in a form
Using the BeanItem class and the binding of component data, i was able to quickly display the first attributes of may data class. However i've hit a wall for tow related attributes :
my class posses a set of available status, as a list of object 'AppStatus'. it also possess a current status, that is one of the status in the 'available' list.
I would like to display the list in the form as a combobox, with the current status selected.
I'we managed to associate the 'available' attribute with a combobox, but i can't seem to be able to fill this combobox when setting the data source (method setItemDataSource). How do i get the avalaible status list and the current status from my Item ?
I could always use a workaround and add a parameter to the method to get the source objet in addition to the BeanItem, but i would prefer to avoid this if the Item properties can give me my attribute.
Regards
Edit : shortened exemple, with code from Eric R.
class Status {
String id;
Sting label
+ setter /getter
}
class App {
String AppId;
String AppLabel
ArrayList<Status> availablestatus;
Status currentStatus
+setter/getter
}
in the form extension, in the createField of the fieldfactory i added the following lines
if ("status".equals(propertyId)) {
// create the combobox
ComboBox status = new ComboBox(
texts.getString("application.label.status"));
status.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
status.setItemCaptionPropertyId("label");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
IndexedContainer container = new IndexedContainer(
(Collection<ApplicationStatus>) item.getItemProperty(
"availableStatus").getValue());
status.setContainerDataSource(container);
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
return status;
} else...
this didn't work, i do get a combobox, with the correct number of lines, but all empties.
i tried to use a beanContainer instead of a IndexedContainer
BeanContainer<String, ApplicationStatus> container =
new BeanContainer<String, ApplicationStatus>(ApplicationStatus.class);
container.addAll((Collection<ApplicationStatus>) item
.getItemProperty("availableStatus").
container.setBeanIdProperty("id");
the result is slightly better, since i do have the available values in the combobox.
only the currentValue is not selected...
I also tried to use a nestedbean property to get the id of the currentstatus, but the result is still not valid... i get a combobox, with the correct value selected, but i can not see others values anymore, since the combobox is readonly ?(even with setReadOnly(false);)
I suggest my way to resolve this. I don't think this is the nicest way, but it's works.
The beanItem class contains all you need.
I did the following in a simple project and it's work verry well :
ComboBox status = new ComboBox("ComboBox");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
for(Status st : (Collection<Status>)item.getItemProperty("availableStatus").getValue()) {
status.addItem(st);
status.setItemCaption(st, st.getLabel());
}
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
Hope it's works.
Regards Éric
From the vaadin demo site you can get this sample that show how to fill a combobox with countries. You could do the same i would guess (not sure I understand your problem 100%):
myForm.setFormFieldFactory(new MyFormFieldFactory ());
private class MyFormFieldFactory extends DefaultFieldFactory {
final ComboBox countries = new ComboBox("Country");
public MyFormFieldFactory () {
countries.setWidth(COMMON_FIELD_WIDTH);
countries.setContainerDataSource(ExampleUtil.getISO3166Container());
countries
.setItemCaptionPropertyId(ExampleUtil.iso3166_PROPERTY_NAME);
countries.setItemIconPropertyId(ExampleUtil.iso3166_PROPERTY_FLAG);
countries.setFilteringMode(ComboBox.FILTERINGMODE_STARTSWITH);
}
#Override
public Field createField(Item item, Object propertyId,
Component uiContext) {
Field f = (Field)item;
if ("countryCode".equals(propertyId)) {
// filtering ComboBox w/ country names
return countries;
}
return f;
}
}
I have a simple row that has 4 columns:
{ [Primary Key Int]RowID, [text]Title, [text]Text, [datetime]Date }
I would like to allow the user to edit this row on a simple page that has a form with the fields "Title" and "Text".
There is a hidden field to store the RowID.
When the user posts this form to my controller action, I want it to update the row's Title and Text, and keep the Date the same. I don't want to have to explicitly include a hidden field for the Date in the form page.
Here is my action:
[AcceptVerbs(HttpVerb.Post)]
public ActionResult EditRow(Row myRow)
{
RowRepository.SaveRow(myRow)
return View("Success");
}
RowRepository:
public void SaveRow(Row myRow)
{
db.MyRows.Attach(myRow);
db.Refresh(RefreshMode.KeepCurrentValues, myRow);
db.SubmitChanges();
}
This dosen't keep the "Date" value already in the row and tries to insert a value that throws an timespan exception.
How can I just tell it to keep the old values?
I tried doing RefreshMode.KeepChanges and nothing.
I'm not in a position to test this at the moment but try making the datetime column nullable and then ensure that the datetime passed into SaveRow has a null value.
Try
[AcceptVerbs(HttpVerb.Post)]
public ActionResult EditRow([Bind(Exclude="Date")] Row myRow) {
RowRepository.SaveRow(myRow)
return View("Success");
}
Update
Try this approach, where there is no 'Date' field on your page
[AcceptVerbs(HttpVerb.Post)]
public ActionResult EditRow(int RowID) {
Row myRow = RowRepository.GetRow(RowID);
UpdateModel(myRow);
RowRepository.Save();
return View("Success");
}
In your repository
public void Save() {
db.SubmitChanges();
}
This will only save the changes made to 'myRow'
You will have add a method in the partial class / override the code it build.
The class Table does implement "INotifyPropertyChanging|ed" which is used to track which column has been changed.
You can hack it and reset the value "this.PropertyChanged".
But what I do at work is a stupid READ-APPLY-WRITE approach (and I am using WebForm).
public void SaveRow(Row myRow)
{
var obj=db.MyRows.Where(c=>c.id==myRow.id).First();
obj.a=myRow.a;
obj.b=myRow.b;
db.SubmitChanges();
}
You can do a bit simpler.
public void SaveRow(Row myRow)
{
db.MyRows.Attach(new Row(){
Id=myRow.Id,
Title=myRow.Title,
Text=myRow.Text,
});
db.SubmitChanges();
}
PS. I am new to LINQ to SQL. Please let me know if there is a smarter way to do it.
Ok, I set it to nullable and it keeps overwriting the database as a null value. I guess its impossible to do this since technically null is a valid value for the column and if I pass an object to the function, the empty values must contain something or be null.
So I would have to explicitly state to take the database value for that column
Thanks