Vaadin 7 : TableFieldFactory - vaadin

Is there a way to implent TableFactory interface with specific fields related to propertyId ?
I only get one type of field since i'm using a generic class for all my tables, and the i'm missing CheckBox boolean value (groovy code):
class DefaultTableFieldFactory implements TableFieldFactory {
#Override
public Field<?> createField(Container container, Object itemId, Object propertyId, Component component) {
TextField t = new TextField()
switch(propertyId) {
case "firstname": t.setNullRepresentation("");
case "lastname": t.setNullRepresentation("");
case "mobile": t.setNullRepresentation("");
case "tel": t.setNullRepresentation("");
case "email": t.setNullRepresentation("");
default: break;
}
t.setWidth("95px")
return t
}
}
So i need to use this class above which implments DefaultTableFieldfactory in order to have the null representation as "" (instead of "null" ) in my whole application.
The goal is to provide for my custom components (more than 30) this null representation in a single place, I want to use this class as my default factory for every table, and connect it like i've done before:
def contacts = (Grails.get(FundService)).getAllContacts(fundId)
def cContainer = new BeanItemContainer<Contact>(Contact.class,contacts)
def t = new Table()
t.containerDataSource = cContainer
t.setTableFieldFactory(new DefaultTableFieldFactory())

Vaadin provides a DefaultTableFieldFactory which does map
Date to a DateField
Boolean to a CheckBox
other to TextField
The DefaultTableFieldFactory is already set on the table. So in your case, if you just want to have CheckBoxes for your boolean fields, I wouldn't implement an own TableFieldFactory. Here's an example:
Table table = new Table();
table.addContainerProperty("text", String.class, "");
table.addContainerProperty("boolean", Boolean.class, false);
table.setEditable(true);
Object itemId = table.addItem();
table.getItem(itemId).getItemProperty("text").setValue("has accepted");
table.getItem(itemId).getItemProperty("boolean").setValue(true);
If you really need to have your own TableFieldFactory then Vaadin recommends:
You could just implement the TableFieldFactory interface, but we
recommend that you extend the DefaultFieldFactory according to your
needs. In the default implementation, the mappings are defined in the
createFieldByPropertyType() method (you might want to look at the
source code) both for tables and forms.
In your code given in the question you always return a TextField. For your missing CheckBoxes you need to return in the specific case a CheckBox.
Don't forget to setEditable(true) when using FieldFactories.
More information here under 5.16.3. Editing the Values in a Table.

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);

Vaadin Bean validation for table

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;
}

Shielding nullable domain properties with ViewModel

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)

vaadin : multiple links in one field on the table

I want to ask about the UI of the vaadin, which is Table.
If I used this component, then I have to create a field using this command:
userTable.addContainerProperty("Status", String.class, "Active");
If I want to create link into this field, then I have to do like this:
userTable.addContainerProperty("Action", Link.class, new Link("Remove", new ExternalResource("#")));
My question is, the example above, only display single link in one field which is REMOVE Link. I want to create two links in one field of that table. For example link for EDIT and DELETE below the "Action" field, how can I do that?
Use a generated column to add the components to each row. Create an Horizontal Layout and two Buttons as the content.
class ValueColumnGenerator implements Table.ColumnGenerator {
String format; /* Format string for the Double values. */
/**
* Creates double value column formatter with the given
* format string.
*/
public ValueColumnGenerator(String format) {
this.format = format;
}
/**
* Generates the cell containing the Double value.
* The column is irrelevant in this use case.
*/
public Component generateCell(Table source, Object itemId,
Object columnId) {
// Get the object stored in the cell as a property
Property prop =
source.getItem(itemId).getItemProperty(columnId);
if (prop.getType().equals(Double.class)) {
HorizontalLayout hbox = new HorizontalLayout()
hbox.addComponent(new Button("Status"))
hbox.addComponent(new Button("Remove"))
return hbox;
}
return null;
}
}
See Section 5.14.5 of the Book of Vaadin for more info:
https://vaadin.com/book/-/page/components.table.html
You can add this buttons to HorizontalLayout or any other container component. Then add this layout to the container property in your table.

Nested bean : a collection inside an object

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;
}
}

Resources