How to listen for value changes on Table Columns

Hi Guys

I have an editable table that has a footer (total), since the table is auditable, I need to update the footer with any changes that happen.

Here’s a scenario : I have a simple shopping list with 4 items, and the items have default prices on them, but I do allow the user to update the prices

I have an item class



public class Item {
    private String description;
    private double cost;
    
    public Item(String description, double cost){
        this.description=description;
        this.cost=cost;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public double getCost() {
        return cost;
    }
    public void setCost(double cost) {
        this.cost = cost;
    }

I have a containter for the items, that initialises the 4items I use


public class ItemContainer extends BeanItemContainer<Item> implements Serializable {

    public ItemContainer() {
        super(Item.class);  
        addBean(new Item("Bread", 6.00));
        addBean(new Item("Milk", 3.00));
        addBean(new Item("Butter", 1.00));
        addBean(new Item("Cereal", 5.00));
    }
    
    public double getTotal(){
        double totalCost=0;
        for (Item item: getAllItemIds()){
            totalCost+=item.getCost();
        }
        
        return totalCost;
    }
}

Then a form with the editable table (note that I make one the columns uneditable, by making it a generated column)



public class ItemFom extends VerticalLayout {

    public static final Object[] NATURAL_COL_ORDER = new Object[]
{"cost"};
    public static final String[] COL_HEADERS_ENGLISH = new String[]
{"COST"};

    public ItemFom() {
        Table table = buildIncomeStatementForm();
        setSizeFull();
        setMargin(true);
        addComponent(table);
        setComponentAlignment(table, Alignment.TOP_CENTER);
    }

    private Table buildIncomeStatementForm() {

        final Table table = new Table("Please fill in the income statement");
        table.setPageLength(4);
        table.setInvalidAllowed(true);
        table.setEditable(true);
        table.setRequired(true);
        table.setRequiredError("Please update required information!");
        table.setImmediate(true);
        table.setContainerDataSource(new ItemContainer());
        table.setVisibleColumns(NATURAL_COL_ORDER);
        table.setColumnHeaders(COL_HEADERS_ENGLISH);
        table.addGeneratedColumn("DESCRIPTION", new Table.ColumnGenerator() {

            @Override
            public Object generateCell(Table source, Object itemId, Object columnId) {
                return ((Item) itemId).getDescription();
            }
        });

        table.setVisibleColumns(new String[]{"DESCRIPTION","cost"});
        table.setSortDisabled(true);
        table.addStyleName(Runo.TABLE_SMALL);

        table.setFooterVisible(true);
        table.setColumnFooter("DESCRIPTION", "TOTAL");
        table.setColumnFooter("cost", ((ItemContainer)table.getContainerDataSource()).getTotal()+"");

        return table;
    }
}

The issue I now have is which event listener do I need to implement to listen for containerdatasource changes, and update my total. I have tried
Property.ValueChangeListener(but thats used for selecting columns), and I’ve also tried Table.ValuChangeListener and that also did not work.


Any assistance is appreciated.

Hi,

Ultimately, you need to register a ValueChange listener on the item being edited.

Have a look at
this thread
, where we talk about monitoring items for any changes. I’ve posted some code : I would use my “Watcher”, and modify the ItemContainer to watch every item added to the container…

HTH a little,

Charles.

As this keeps coming up: the value of a Table is the id of the selected row (or Set of ids of selected rows in multi-select mode). In this regard, it behaves like other selection components such as ListSelect.

There is no API in Table for listening for changes of the values of individual properties in the data source of the table, nor can there ever be if lazy loading for large tables is to be supported. Any such listeners would either render lazy loading of data ineffective or require special (case specific) support from the underlying container.

In theory, there could be listener support for changes of values of the display buffer, but that would also be somewhat complicated and it might not be clear to the user what actually is in the (scrollable, somewhat larger than screen) buffer and what is not at any given time.

Hi Guys,

Thanx so much for your quick responses, you guys are awesome and I really appreciate it.
I have however gone with registering cost column as a Textfield, and hooking into the TextField ValueChangeListener. This solution is much simpler
for us as we stick to the core product as much as possible.

I however would have preferred a solution where I could hook directly into the table.

Here’s the solution as currently implemented.


package za.org.seda.ttf.sedagate.main.ui.playpen;

import com.vaadin.ui.TextField;

public class Item {
    private String description;

    /* Changed it from double to TextField, this is so that I can register ValueChangeListener later */
    private TextField cost;
    
    public Item(String description, TextField cost){
        this.description=description;
        this.cost=cost;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public TextField getCost() {
        return cost;
    }
    public void setCost(TextField cost) {
        this.cost = cost;
    }
}

I then changed my container to use the new TextField cost.


public class ItemContainer extends BeanItemContainer<Item> implements Serializable {

    public ItemContainer() {
        super(Item.class);
        addBean(new Item("Bread", new TextField()));
        addBean(new Item("Milk", new TextField()));
        addBean(new Item("Butter", new TextField()));
        addBean(new Item("Cereal", new TextField()));
    }

    public double getTotal() {
        double totalCost = 0;
        for (Item item : getAllItemIds()) {
            if ((item.getCost().getValue() !=null)&& item.getCost().getValue().toString().length()>0) {
                totalCost += new Double(item.getCost().getValue().toString());
            }
        }

        return totalCost;
    }
}

Lastly I updated my form to register ValueChangeListener on all the text fields generated in my container. On ValueChange, I reset the footer

Here’s the Key code that does it in ItemForm


        for (Object o : table.getItemIds()){
            Item item=(Item)o;
            item.getCost().setImmediate(true);
            item.getCost().addListener(new Property.ValueChangeListener() {
                @Override
                public void valueChange(ValueChangeEvent event) {
                     table.setColumnFooter("cost", ((ItemContainer)table.getContainerDataSource()).getTotal()+"");
                }
            });
        }

public class ItemFom extends VerticalLayout {

    public static final Object[] NATURAL_COL_ORDER = new Object[]
{"cost"};
    public static final String[] COL_HEADERS_ENGLISH = new String[]
{"COST"};

    public ItemFom() {
        Table table = buildIncomeStatementForm();
        setSizeFull();
        setMargin(true);
        addComponent(table);
        setComponentAlignment(table, Alignment.TOP_CENTER);
    }

    private Table buildIncomeStatementForm() {

        final Table table = new Table("Please update prices");
        table.setPageLength(4);
        table.setInvalidAllowed(true);
        table.setEditable(true);
        table.setRequired(true);
        table.setImmediate(true);
        table.setContainerDataSource(new ItemContainer());
        table.setVisibleColumns(NATURAL_COL_ORDER);
        table.setColumnHeaders(COL_HEADERS_ENGLISH);
        table.addGeneratedColumn("DESCRIPTION", new Table.ColumnGenerator() {

            @Override
            public Object generateCell(Table source, Object itemId, Object columnId) {
                return ((Item) itemId).getDescription();
            }
        });

        
        table.setVisibleColumns(new String[]{"DESCRIPTION","cost"});
        table.setSortDisabled(true);
        table.addStyleName(Runo.TABLE_SMALL);

        table.setFooterVisible(true);
        table.setColumnFooter("DESCRIPTION", "TOTAL");
        table.setColumnFooter("cost", ((ItemContainer)table.getContainerDataSource()).getTotal()+"");

        
        for (Object o : table.getItemIds()){
            Item item=(Item)o;
            item.getCost().setImmediate(true);
            item.getCost().addListener(new Property.ValueChangeListener() {
                @Override
                public void valueChange(ValueChangeEvent event) {
                     table.setColumnFooter("cost", ((ItemContainer)table.getContainerDataSource()).getTotal()+"");
                }
            });
        }
        

        return table;
    }
}