GeneratedColumnExample.java
/* 
 * Copyright 2009 IT Mill Ltd.
 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 
 * http://www.apache.org/licenses/LICENSE-2.0
 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.itmill.toolkit.demo.featurebrowser;

import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Vector;

import com.itmill.toolkit.data.Container;
import com.itmill.toolkit.data.Item;
import com.itmill.toolkit.data.Property;
import com.itmill.toolkit.data.Container.Indexed;
import com.itmill.toolkit.data.util.BeanItem;
import com.itmill.toolkit.ui.AbstractField;
import com.itmill.toolkit.ui.BaseFieldFactory;
import com.itmill.toolkit.ui.CheckBox;
import com.itmill.toolkit.ui.Component;
import com.itmill.toolkit.ui.CustomComponent;
import com.itmill.toolkit.ui.Field;
import com.itmill.toolkit.ui.Label;
import com.itmill.toolkit.ui.Table;
import com.itmill.toolkit.ui.VerticalLayout;
import com.itmill.toolkit.ui.Button.ClickEvent;
import com.itmill.toolkit.ui.Button.ClickListener;

/**
 * This example demonstrates the use of generated columns in a table. Generated
 * columns can be used for formatting values or calculating them from other
 * columns (or properties of the items).
 
 * For the data model, we use POJOs bound to a custom Container with BeanItem
 * items.
 
 @author magi
 */
public class GeneratedColumnExample extends CustomComponent {
    /**
     * The business model: fill-up at a gas station.
     */
    public class FillUp {
        Date date;
        double quantity;
        double total;

        public FillUp() {
        }

        public FillUp(int day, int month, int year, double quantity,
                double total) {
            date = new GregorianCalendar(year, month - 1, day).getTime();
            this.quantity = quantity;
            this.total = total;
        }

        /** Calculates price per unit of quantity (âぎ/l). */
        public double price() {
            if (quantity != 0.0) {
                return total / quantity;
            else {
                return 0.0;
            }
        }

        /** Calculates average daily consumption between two fill-ups. */
        public double dailyConsumption(FillUp other) {
            double difference_ms = date.getTime() - other.date.getTime();
            double days = difference_ms / 1000 3600 24;
            if (days < 0.5) {
                days = 1.0// Avoid division by zero if two fill-ups on the
                // same day.
            }
            return quantity / days;
        }

        /** Calculates average daily consumption between two fill-ups. */
        public double dailyCost(FillUp other) {
            return price() * dailyConsumption(other);
        }

        // Getters and setters

        public Date getDate() {
            return date;
        }

        public void setDate(Date date) {
            this.date = date;
        }

        public double getQuantity() {
            return quantity;
        }

        public void setQuantity(double quantity) {
            this.quantity = quantity;
        }

        public double getTotal() {
            return total;
        }

        public void setTotal(double total) {
            this.total = total;
        }
    };

    /**
     * This is a custom container that allows adding BeanItems inside it. The
     * BeanItem objects must be bound to an object. The item ID is an Integer
     * from 0 to 99.
     
     * Most of the interface methods are implemented with just dummy
     * implementations, as they are not needed in this example.
     */
    public class MySimpleIndexedContainer implements Container, Indexed {
        Vector items;
        Object itemtemplate;

        public MySimpleIndexedContainer(Object itemtemplate) {
            this.itemtemplate = itemtemplate;
            items = new Vector()// Yeah this is just a test
        }

        public boolean addContainerProperty(Object propertyId, Class type,
                Object defaultValuethrows UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        public Item addItem(Object itemIdthrows UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        public Object addItem() throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        /**
         * This addItem method is specific for this container and allows adding
         * BeanItem objects. The BeanItems must be bound to MyBean objects.
         */
        public void addItem(BeanItem itemthrows UnsupportedOperationException {
            items.add(item);
        }

        public boolean containsId(Object itemId) {
            if (itemId instanceof Integer) {
                int pos = ((IntegeritemId).intValue();
                if (pos >= && pos < items.size()) {
                    return items.get(pos!= null;
                }
            }
            return false;
        }

        /**
         * The Table will call this method to get the property objects for the
         * columns. It uses the property objects to determine the data types of
         * the columns.
         */
        public Property getContainerProperty(Object itemId, Object propertyId) {
            if (itemId instanceof Integer) {
                int pos = ((IntegeritemId).intValue();
                if (pos >= && pos < items.size()) {
                    Item item = (Itemitems.get(pos);

                    // The BeanItem provides the property objects for the items.
                    return item.getItemProperty(propertyId);
                }
            }
            return null;
        }

        /** Table calls this to get the column names. */
        public Collection getContainerPropertyIds() {
            Item item = new BeanItem(itemtemplate);

            // The BeanItem knows how to get the property names from the bean.
            return item.getItemPropertyIds();
        }

        public Item getItem(Object itemId) {
            if (itemId instanceof Integer) {
                int pos = ((IntegeritemId).intValue();
                if (pos >= && pos < items.size()) {
                    return (Itemitems.get(pos);
                }
            }
            return null;
        }

        public Collection getItemIds() {
            Vector ids = new Vector(items.size());
            for (int i = 0; i < items.size(); i++) {
                ids.add(Integer.valueOf(i));
            }
            return ids;
        }

        public Class getType(Object propertyId) {
            return BeanItem.class;
        }

        public boolean removeAllItems() throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        public boolean removeContainerProperty(Object propertyId)
                throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        public boolean removeItem(Object itemId)
                throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }

        public int size() {
            return items.size();
        }

        public Object addItemAt(int indexthrows UnsupportedOperationException {
            // TODO Auto-generated method stub
            return null;
        }

        public Item addItemAt(int index, Object newItemId)
                throws UnsupportedOperationException {
            // TODO Auto-generated method stub
            return null;
        }

        public Object getIdByIndex(int index) {
            return Integer.valueOf(index);
        }

        public int indexOfId(Object itemId) {
            return ((IntegeritemId).intValue();
        }

        public Object addItemAfter(Object previousItemId)
                throws UnsupportedOperationException {
            // TODO Auto-generated method stub
            return null;
        }

        public Item addItemAfter(Object previousItemId, Object newItemId)
                throws UnsupportedOperationException {
            // TODO Auto-generated method stub
            return null;
        }

        public Object firstItemId() {
            return new Integer(0);
        }

        public boolean isFirstId(Object itemId) {
            return ((IntegeritemId).intValue() == 0;
        }

        public boolean isLastId(Object itemId) {
            return ((IntegeritemId).intValue() == (items.size() 1);
        }

        public Object lastItemId() {
            return new Integer(items.size() 1);
        }

        public Object nextItemId(Object itemId) {
            int pos = indexOfId(itemId);
            if (pos >= items.size() 1) {
                return null;
            }
            return getIdByIndex(pos + 1);
        }

        public Object prevItemId(Object itemId) {
            int pos = indexOfId(itemId);
            if (pos <= 0) {
                return null;
            }
            return getIdByIndex(pos - 1);
        }
    }

    /** Formats the dates in a column containing Date objects. */
    class DateColumnGenerator implements Table.ColumnGenerator {
        /**
         * Generates the cell containing the Date value. The column is
         * irrelevant in this use case.
         */
        public Component generateCell(Table source, Object itemId,
                Object columnId) {
            Property prop = source.getItem(itemId).getItemProperty(columnId);
            if (prop.getType().equals(Date.class)) {
                Label label = new Label(String.format("%tF",
                        new Object[] { (Dateprop.getValue() }));
                label.addStyleName("column-type-date");
                return label;
            }

            return null;
        }
    }

    /** Formats the value in a column containing Double objects. */
    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) {
            Property prop = source.getItem(itemId).getItemProperty(columnId);
            if (prop.getType().equals(Double.class)) {
                Label label = new Label(String.format(format,
                        new Object[] { (Doubleprop.getValue() }));

                // Set styles for the column: one indicating that it's a value
                // and a more
                // specific one with the column name in it. This assumes that
                // the column
                // name is proper for CSS.
                label.addStyleName("column-type-value");
                label.addStyleName("column-" (StringcolumnId);
                return label;
            }
            return null;
        }
    }

    /** Table column generator for calculating price column. */
    class PriceColumnGenerator implements Table.ColumnGenerator {
        public Component generateCell(Table source, Object itemId,
                Object columnId) {
            // Retrieve the item.
            BeanItem item = (BeanItemsource.getItem(itemId);

            // Retrieves the underlying POJO from the item.
            FillUp fillup = (FillUpitem.getBean();

            // Do the business logic
            double price = fillup.price();

            // Create the generated component for displaying the calcucated
            // value.
            Label label = new Label(String.format("%1.2f âぎ",
                    new Object[] { new Double(price) }));

            // We set the style here. You can't use a CellStyleGenerator for
            // generated columns.
            label.addStyleName("column-price");
            return label;
        }
    }

    /** Table column generator for calculating consumption column. */
    class ConsumptionColumnGenerator implements Table.ColumnGenerator {
        /**
         * Generates a cell containing value calculated from the item.
         */
        public Component generateCell(Table source, Object itemId,
                Object columnId) {
            Indexed indexedSource = (Indexedsource.getContainerDataSource();

            // Can not calculate consumption for the first item.
            if (indexedSource.isFirstId(itemId)) {
                Label label = new Label("N/A");
                label.addStyleName("column-consumption");
                return label;
            }

            // Index of the previous item.
            Object prevItemId = indexedSource.prevItemId(itemId);

            // Retrieve the POJOs.
            FillUp fillup = (FillUp) ((BeanItemindexedSource.getItem(itemId))
                    .getBean();
            FillUp prev = (FillUp) ((BeanItemsource.getItem(prevItemId))
                    .getBean();

            // Do the business logic
            return generateCell(fillup, prev);
        }

        public Component generateCell(FillUp fillup, FillUp prev) {
            double consumption = fillup.dailyConsumption(prev);

            // Generate the component for displaying the calculated value.
            Label label = new Label(String.format("%3.2f l",
                    new Object[] { new Double(consumption) }));

            // We set the style here. You can't use a CellStyleGenerator for
            // generated columns.
            label.addStyleName("column-consumption");
            return label;
        }
    }

    /** Table column generator for calculating daily cost column. */
    class DailyCostColumnGenerator extends ConsumptionColumnGenerator {
        @Override
        public Component generateCell(FillUp fillup, FillUp prev) {
            double dailycost = fillup.dailyCost(prev);

            // Generate the component for displaying the calculated value.
            Label label = new Label(String.format("%3.2f âぎ",
                    new Object[] { new Double(dailycost) }));

            // We set the style here. You can't use a CellStyleGenerator for
            // generated columns.
            label.addStyleName("column-dailycost");
            return label;
        }
    }

    /**
     * Custom field factory that sets the fields as immediate.
     */
    public class ImmediateFieldFactory extends BaseFieldFactory {
        @Override
        public Field createField(Class type, Component uiContext) {
            // Let the BaseFieldFactory create the fields
            Field field = super.createField(type, uiContext);

            // ...and just set them as immediate
            ((AbstractFieldfield).setImmediate(true);

            return field;
        }
    }

    public GeneratedColumnExample() {
        final Table table = new Table();

        // Define table columns. These include also the column for the generated
        // column, because we want to set the column label to something
        // different than the property ID.
        table
                .addContainerProperty("date", Date.class, null, "Date", null,
                        null);
        table.addContainerProperty("quantity", Double.class, null,
                "Quantity (l)", null, null);
        table.addContainerProperty("price", Double.class, null, "Price (âぎ/l)",
                null, null);
        table.addContainerProperty("total", Double.class, null, "Total (âぎ)",
                null, null);
        table.addContainerProperty("consumption", Double.class, null,
                "Consumption (l/day)", null, null);
        table.addContainerProperty("dailycost", Double.class, null,
                "Daily Cost (âぎ/day)", null, null);

        // Define the generated columns and their generators.
        table.addGeneratedColumn("date"new DateColumnGenerator());
        table
                .addGeneratedColumn("quantity"new ValueColumnGenerator(
                        "%.2f l"));
        table.addGeneratedColumn("price"new PriceColumnGenerator());
        table.addGeneratedColumn("total"new ValueColumnGenerator("%.2f âぎ"));
        table.addGeneratedColumn("consumption",
                new ConsumptionColumnGenerator());
        table.addGeneratedColumn("dailycost"new DailyCostColumnGenerator());

        // Create a data source and bind it to the table.
        MySimpleIndexedContainer data = new MySimpleIndexedContainer(
                new FillUp());
        table.setContainerDataSource(data);

        // Generated columns are automatically placed after property columns, so
        // we have to set the order of the columns explicitly.
        table.setVisibleColumns(new Object[] { "date""quantity""price",
                "total""consumption""dailycost" });

        // Add some data.
        data.addItem(new BeanItem(new FillUp(192200544.9651.21)));
        data.addItem(new BeanItem(new FillUp(303200544.9153.67)));
        data.addItem(new BeanItem(new FillUp(204200542.9649.06)));
        data.addItem(new BeanItem(new FillUp(235200547.3755.28)));
        data.addItem(new BeanItem(new FillUp(66200535.3441.52)));
        data.addItem(new BeanItem(new FillUp(306200516.0720.00)));
        data.addItem(new BeanItem(new FillUp(27200536.4036.19)));
        data.addItem(new BeanItem(new FillUp(67200539.1750.90)));
        data.addItem(new BeanItem(new FillUp(277200543.4353.03)));
        data.addItem(new BeanItem(new FillUp(17820052029.18)));
        data.addItem(new BeanItem(new FillUp(308200546.0659.09)));
        data.addItem(new BeanItem(new FillUp(229200546.1160.36)));
        data.addItem(new BeanItem(new FillUp(1410200541.5150.19)));
        data.addItem(new BeanItem(new FillUp(1211200535.2440.00)));
        data.addItem(new BeanItem(new FillUp(2811200545.2653.27)));

        // Have a check box that allows the user to make the quantity
        // and total columns editable.
        final CheckBox editable = new CheckBox(
                "Edit the input values - calculated columns are regenerated");
        editable.setImmediate(true);
        editable.addListener(new ClickListener() {
            public void buttonClick(ClickEvent event) {
                table.setEditable(editable.booleanValue());

                // The columns may not be generated when we want to have them
                // editable.
                if (editable.booleanValue()) {
                    table.removeGeneratedColumn("quantity");
                    table.removeGeneratedColumn("total");
                else {
                    // In non-editable mode we want to show the formatted
                    // values.
                    table.addGeneratedColumn("quantity",
                            new ValueColumnGenerator("%.2f l"));
                    table.addGeneratedColumn("total"new ValueColumnGenerator(
                            "%.2f âぎ"));
                }
                // The visible columns are affected by removal and addition of
                // generated columns so we have to redefine them.
                table.setVisibleColumns(new Object[] { "date""quantity",
                        "price""total""consumption""dailycost" });
            }
        });

        // Use a custom field factory to set the edit fields as immediate.
        // This is used when the table is in editable mode.
        table.setFieldFactory(new ImmediateFieldFactory());

        // Setting the table itself as immediate has no relevance in this
        // example,
        // because it is relevant only if the table is selectable and we want to
        // get the selection changes immediately.
        table.setImmediate(true);

        table.setHeight("300px");

        VerticalLayout layout = new VerticalLayout();
        layout.setMargin(true);
        layout
                .addComponent(new Label(
                        "Table with column generators that format and calculate cell values."));
        layout.addComponent(table);
        layout.addComponent(editable);
        layout.addComponent(new Label(
                "Columns displayed in blue are calculated from Quantity and Total. "
                        "Others are simply formatted."));
        layout.setExpandRatio(table, 1);
        layout.setSizeUndefined();
        setCompositionRoot(layout);
        // setSizeFull();
    }
}