Table with a FieldFactory

Hi! I’m implementing a table with a field factory attached to it. I’m probably doing something very simple wrong as I can’t get right data to be shown in the table; and no data is being written to the objects. (Additionally I got a working example but can’t figure out what i’m doing differently.)

Here’s the code:


package hello;

import java.util.Collection;

import com.itmill.toolkit.Application;
import com.itmill.toolkit.data.Item;
import com.itmill.toolkit.data.util.IndexedContainer;
import com.itmill.toolkit.ui.AbstractField;
import com.itmill.toolkit.ui.BaseFieldFactory;
import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.Component;
import com.itmill.toolkit.ui.Field;
import com.itmill.toolkit.ui.HorizontalLayout;
import com.itmill.toolkit.ui.Label;
import com.itmill.toolkit.ui.Select;
import com.itmill.toolkit.ui.Table;
import com.itmill.toolkit.ui.TextField;
import com.itmill.toolkit.ui.VerticalLayout;
import com.itmill.toolkit.ui.Window;
import com.itmill.toolkit.ui.Button.ClickEvent;
import com.itmill.toolkit.ui.Button.ClickListener;

public class MyView extends Application implements ClickListener {

    IndexedContainer container;
    VerticalLayout resultList = new VerticalLayout();
    private Button showContent;
    private Button addRow;

    @Override
    public void init() {
        setTheme("Hello");
        Window main = new Window("The Main Window");
        setMainWindow(main);
        Table table = new Table();

        // table.addGeneratedColumn("combination", new ValueColumnGenerator());

        table.setFieldFactory(new BaseFieldFactory() {

            @Override
            public Field createField(Item item, Object propertyId,
                    Component uiContext) {
                String pid = (String) propertyId;
                if (pid.equals("first")) {
                    TextField field = new TextField();
                    field.setImmediate(true);
                    return field;
                } else if (pid.equals("second")) {
                    Select field = new Select();
                    field.setImmediate(true);
                    field.addItem("there");
                    field.addItem("world");
                    return field;
                } else {
                    return null;
                }
            }
        });
        container = getContainer();
        table.setContainerDataSource(container);
        table.setEditable(true);
        table.setWriteThrough(true);
        table.setImmediate(true);
        main.addComponent(table);
        showContent = new Button("Show all content", this);
        addRow = new Button("Add row", this);
        main.addComponent(showContent);
        main.addComponent(addRow);
        main.addComponent(resultList);
    }

    public IndexedContainer getContainer() {

        IndexedContainer container = new IndexedContainer();
        container.addContainerProperty("first", String.class, null);
        container.addContainerProperty("second", String.class, null);
        Hello hello = new Hello();
        hello.setFirst("hello");
        hello.setSecond("there");
        Item item = container.addItem(hello);
        return container;
    }

    public class Hello {

        public String first;
        public String second;

        public Hello() {
            first = "f";
            second = "l";
        }

        public String getFirst() {
            return first;
        }

        public void setFirst(String first) {
            this.first = first;
        }

        public String getSecond() {
            return second;
        }

        public void setSecond(String second) {
            this.second = second;
        }

    }

    /** Formats the value in a column containing Double objects. */
    class ValueColumnGenerator implements Table.ColumnGenerator {
        /**
         * Generates the cell containing the Double value. The column is
         * irrelevant in this use case.
         */
        public Component generateCell(Table source, Object itemId,
                Object columnId) {
            Hello hello = (Hello) itemId;
            return new Label(hello.getFirst() + " " + hello.getSecond());
        }
    }

    public void buttonClick(ClickEvent event) {
        if (event.getButton() == showContent) {
            resultList.removeAllComponents();
            Collection<Hello> c = container.getItemIds();
            for (Hello hello : c) {
                HorizontalLayout hl = new HorizontalLayout();
                hl.addComponent(new Label(hello.getFirst()));
                hl.addComponent(new Label(hello.getSecond()));
                resultList.addComponent(hl);
            }
        } else if (event.getButton() == addRow) {
            Hello h = new Hello();
            Item i = container.addItem(h);
            // i.getItemProperty("first").setValue(h.getFirst());
            // i.getItemProperty("second").setValue(h.getSecond());

        }
    }

}

There’s a ColumnGenerator in there but the use of it is commented out as it does not have anything to do with this problem.

Current result: The table has the “first” and “second” columns, and they have both TextFields (second should have a select). The textfields have always the value “null” while the data behind this is not null. Changing null into something else does not write the data into the objects.

I also noticed now that adding a breakpoint into the fieldfactory’s createField(Item item, Object propertyId, Component uiContext) did not have any effect; it never gets to it.

Reading the manual I noticed that the fieldfactory uses the first of its four functions that does
not
return null. I override the other three:


            @Override
            public Field createField(Class type, Component uiContext) {
                return null;
            }

            @Override
            public Field createField(Container container, Object itemId,
                    Object propertyId, Component uiContext) {
                return null;
            }

            @Override
            public Field createField(Property property, Component uiContext) {
                return null;
            }

I put a breakpoint into every return -statement and the only breakpoint catched is createField(Container container, Object itemId, Object propertyId, Component uiContext). The fieldfactory still doesn’t continue to the other possible createFields. Now I don’t get the textfields eighter, the fields are just “null” (shown as an empty row), just what the one createField returns

You should probably override this

public Field createField(Container container, Object itemId,
                    Object propertyId, Component uiContext) {

Second, your fields bind to containers items not itemId’s members.
Your container looks like this:
ItemId | “first” | “second”
Object | null | null

Fields you generate bind to those null values.

To bind to itemIds members you have to set fields datasource by hand with

 field.setPropertyDataSource(new MethodProperty(hello,"first"));

If you want to bind directly to Hello objects members, you cannot use field factory! Table overrides fields datasources.

Thanks for the tip Mauno! I still have the problem that the fieldfactory doesn’t call on the right createField. I tried to change from using BaseFieldFactory to implementing the FieldFactory-interface directly. All others return now directly null, but it still doesn’t get into the createField that I need. :roll:

Ok, tested and this works for me.
I Made two changes, changed override method and added property values to generated container in getContainer()

public class MyView extends Application implements ClickListener {

    IndexedContainer container;
    VerticalLayout resultList = new VerticalLayout();
    private Button showContent;
    private Button addRow;

    @Override
    public void init() {
        setTheme("Hello");
        Window main = new Window("The Main Window");
        setMainWindow(main);
        Table table = new Table();

        // table.addGeneratedColumn("combination", new ValueColumnGenerator());

        table.setFieldFactory(new BaseFieldFactory() {

            @Override
            public Field createField(Container container, Object itemId,
                    Object propertyId, Component uiContext) {
                String pid = (String) propertyId;
                if (pid.equals("first")) {
                    TextField field = new TextField();
                    field.setImmediate(true);
                    return field;
                } else if (pid.equals("second")) {
                    Select field = new Select();
                    field.setImmediate(true);
                    field.addItem("there");
                    field.addItem("world");
                    return field;
                } else {
                    return null;
                }
            }
        });
        container = getContainer();
        table.setContainerDataSource(container);
        table.setEditable(true);
        table.setWriteThrough(true);
        table.setImmediate(true);
        main.addComponent(table);
        showContent = new Button("Show all content", this);
        addRow = new Button("Add row", this);
        main.addComponent(showContent);
        main.addComponent(addRow);
        main.addComponent(resultList);
    }

    public IndexedContainer getContainer() {

        IndexedContainer container = new IndexedContainer();
        container.addContainerProperty("first", String.class, null);
        container.addContainerProperty("second", String.class, null);
        Hello hello = new Hello();
        hello.setFirst("hello");
        hello.setSecond("there");
        Item item = container.addItem(hello);
        item.getItemProperty("first").setValue("testing fieldfactory");
        item.getItemProperty("second").setValue("there");
        return container;
    }

    public class Hello {

        public String first;
        public String second;

        public Hello() {
            first = "f";
            second = "l";
        }

        public String getFirst() {
            return first;
        }

        public void setFirst(String first) {
            this.first = first;
        }

        public String getSecond() {
            return second;
        }

        public void setSecond(String second) {
            this.second = second;
        }

    }

    /** Formats the value in a column containing Double objects. */
    class ValueColumnGenerator implements Table.ColumnGenerator {
        /**
         * Generates the cell containing the Double value. The column is
         * irrelevant in this use case.
         */
        public Component generateCell(Table source, Object itemId,
                Object columnId) {
            Hello hello = (Hello) itemId;
            return new Label(hello.getFirst() + " " + hello.getSecond());
        }
    }

    public void buttonClick(ClickEvent event) {
        if (event.getButton() == showContent) {
            resultList.removeAllComponents();
            Collection<Hello> c = container.getItemIds();
            for (Hello hello : c) {
                HorizontalLayout hl = new HorizontalLayout();
                hl.addComponent(new Label(hello.getFirst()));
                hl.addComponent(new Label(hello.getSecond()));
                resultList.addComponent(hl);
            }
        } else if (event.getButton() == addRow) {
            Hello h = new Hello();
            Item i = container.addItem(h);
            // i.getItemProperty("first").setValue(h.getFirst());
            // i.getItemProperty("second").setValue(h.getSecond());

        }
    }

}

Thanks! Did the write-through work for you? I can’t get any textfield data to be written into the original object. And why use getProperty.setValue as these values should come automatically from the object. I tested with the setdatasource on the fields, adding writethrough on them etc. and nothing changes the original objects values. (Shown by clicking the “Show all content” -button)

You’ll want to bind properties to the POJO - otherwise the changes will be written to the property, but not to the pojo. This will remove the need to set the property value manually as well.

That is: the container (or table) does not know that the itemId happens to be the pojo you want to update - the item id could be anything. You could update the pojo manually from the container properties when saving, but the ‘right’ way to do it is to bind the property to the object value, for instance using ObjectProperty, MethodProperty, or BeanItem.

The usual disclaimer applies: the above applies iff I understood the problem correctly during my quick one-pass read just now… :wink:

// Marc

Hello,

I am facing the same problem now and I am not able to find the solution according this thread. I can not bind my CustomField (called CompanyTransferField) to the property. The field is shown in the table but the new value is not written to the database via JPAContainer. Could you help me please?

Here is my FieldFactory:

    @SuppressWarnings({ "serial", "unchecked", "rawtypes" })
    public static FieldFactory makeFieldFactory(final String[] editablePropertyIds, final Class<?> beanClass) {
        FieldFactory fieldFactory = new FieldFactory() {            
            public Field createField(Container container, Object itemId, Object propertyId,
                    com.vaadin.ui.Component uiContext) {
                
                Field field;
                Property property = container.getItem(itemId).getItemProperty(propertyId);
                String strPid = String.valueOf(propertyId);
                boolean readOnly = true;
                
                if  (strPid.equals("supplier")) {
                    field = new CompanyTransferField("", InputFieldMode.NAME_ONLY);
//                    field.setPropertyDataSource(new ObjectProperty<Company>((Company) property.getValue(), Company.class));
//                    field.setPropertyDataSource(property);
                }
                else if (strPid.equals("project"))
                    field = new ProjectTransferField("");
                else
                    field = FieldFactory.get().createField(container, itemId, propertyId, uiContext);
                
                
                for(String s:editablePropertyIds) {
                    if (propertyId.equals(s)) {
                        readOnly = false;
                        field.addValidator(new BeanValidator(beanClass, s));
                        break;        
                    }
                }
                    
                if (readOnly)
                    field.setReadOnly(true);
                else
                    field.setReadOnly(false);
                
                return field;
            } //createField
        }; // fieldFactory
        
        return fieldFactory;
        
    } //makeFieldFactory

The previous post in the thread was five years ago and things related to data binding have evolved since then - especially if you are using Vaadin 7.

First see
the section on Table in the Book of Vaadin
, including the field factory example. Note also that binding the generated field to the data source is not the responsibility of the field factory but is done automatically after the field factory is used to create the field instance. Likewise, if I remember correctly, setReadOnly() is called automatically based on whether the property is read-only or not when the property is bound.

If the value is not stored, also check that your fields (including those inside the CustomField) have the immediate flag set. The implementation of your CustomField might also be related to your problem.