Why are there no decent examples of CompositeCell in use within a CellTable?
Asked Answered
J

1

5

I have scoured GoogleCode, GWT ShowCase, GWT Developer Notes, and Google Groups for some inkling as to how to get/set values of a CompositeCell. There is no one definitive example that explains how to use it within a CellTable.

Let's stare at some code... first an abstract class...

public abstract class ToggleableGrid<T> extends CellTable<T> {

private static final String DEFAULT_TABLE_WIDTH = "100%";
private static final DisplayMode DEFAULT_MODE = DisplayMode.VIEW;

protected void setDefaults() {
    setWidth(DEFAULT_TABLE_WIDTH);
    // Set the message to display when the table is empty.
    setEmptyTableWidget(new Label(UiMessages.INSTANCE.no_results()));
    // Add a selection model so we can select cells
    final SelectionModel<T> selectionModel = new MultiSelectionModel<T>();
    setSelectionModel(selectionModel, DefaultSelectionEventManager.<T> createDefaultManager());
}

public void setInput(List<T> content) {
    setInput(content, DEFAULT_MODE);
}

public void setInput(List<T> content, DisplayMode mode) {
    setDefaults();
    resetTableColumns();
    final ListDataProvider<T> dataProvider = new ListDataProvider<T>(content);
    final ListHandler<T> sortHandler = new ListHandler<T>(dataProvider.getList());
    addColumnSortHandler(sortHandler);
    initializeStructure(constructMetadata(), sortHandler, mode);
    dataProvider.addDataDisplay(this);
}

// see https://mcmap.net/q/1175293/-remove-all-columns-from-a-celltable
// concrete classes are forced to maintain a handle on all columns added
private void resetTableColumns() {
    for (final Column<T, ?> column: allColumns()) {
        removeColumn(column);
    }
    allColumns().clear();
}

public boolean isInEditMode(DisplayMode currentDisplayMode) {
    boolean result = false;
    if (currentDisplayMode == DisplayMode.EDIT) {
        result = true;
    }
    return result;
}

protected abstract Set<Column<T, ?>> allColumns();

protected abstract TableMetadata constructMetadata();

protected abstract void initializeStructure(TableMetadata metadata, ListHandler<T> sortHandler, DisplayMode mode);

protected void setColumnHorizontalAlignment(Column<T, ?> column, HorizontalAlignmentConstant alignment) {
    column.setHorizontalAlignment(alignment);
}

@Override
public void addColumn(Column<T, ?> column, String columnHeaderName) {
    final StringBuffer sb = new StringBuffer();
    sb.append("<div align=\"right\">").append(columnHeaderName).append("</div>");
    final SafeHtml header = new OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml(sb.toString());
    addColumn(column, header);
    allColumns().add(column);
}

}

And then a concrete implementation...

public class EnergyOfferGrid extends ToggleableGrid<EnergyOfferDTO> {

private static final int MAX_NUMBER_OF_MW_PRICE_POINTS = 10;

private Set<Column<EnergyOfferDTO, ?>> columns = new HashSet<Column<EnergyOfferDTO, ?>>();

@Override
protected Set<Column<EnergyOfferDTO, ?>> allColumns() {
    return columns;
}

@Override
protected TableMetadata constructMetadata() {
    final TableMetadata metadata = new TableMetadata();

    // TODO Consider a predefined set of ReferenceData to be held in a common package

    // Use Slope
    metadata.addColumnMetadata(UiMessages.INSTANCE.use_slope(), new String[] {UiMessages.INSTANCE.yes(), UiMessages.INSTANCE.no()}, new String[] {"true", "false"});

    return metadata;
}

@Override
protected void initializeStructure(TableMetadata metadata, ListHandler<EnergyOfferDTO> sortHandler, DisplayMode currentDisplayMode) {
    addHourColumn(sortHandler);
    addUseSlopeColumn(metadata, sortHandler, currentDisplayMode);
    for (int i = 1; i <= MAX_NUMBER_OF_MW_PRICE_POINTS; i++) {
        addPriceMwColumn(i, currentDisplayMode);
    }
}

protected void addHourColumn(ListHandler<EnergyOfferDTO> sortHandler) {
    final Column<EnergyOfferDTO, String> hourColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {

        @Override
        public String getValue(EnergyOfferDTO energyOffer) {
            String result = "";
            final String isoDateTime = energyOffer.getDateTime();
            if (isoDateTime != null && !isoDateTime.isEmpty()) {
                final Date dateTime = TimeUtil.isoToDate(isoDateTime);
                if (dateTime != null) {
                    result = TimeUtil.dateToHour(dateTime);
                }
            }
            return result;
        }

    };
    hourColumn.setSortable(true);
    sortHandler.setComparator(hourColumn, new Comparator<EnergyOfferDTO>() {
        @Override
        public int compare(EnergyOfferDTO eo1, EnergyOfferDTO eo2) {
            return eo1.getDateTime().compareTo(eo2.getDateTime());
        }
    });

    addColumn(hourColumn, UiMessages.INSTANCE.hour());
    setColumnWidth(hourColumn, 10, Unit.PCT);
    setColumnHorizontalAlignment(hourColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}


protected void addUseSlopeColumn(TableMetadata metadata, ListHandler<EnergyOfferDTO> sortHandler, DisplayMode currentDisplayMode) {
    final ReferenceData refData = metadata.allColumnMetadata().get(UiMessages.INSTANCE.use_slope());
    Column<EnergyOfferDTO, String> useSlopeColumn;
    Cell<String> cell;
    if (isInEditMode(currentDisplayMode)) {
        cell = new ReferenceDataBackedSelectionCell(refData);
    } else {
        cell = new TextCell();
    }
    useSlopeColumn = new Column<EnergyOfferDTO, String>(cell) {

        @Override
        public String getValue(EnergyOfferDTO energyOffer) {
            return refData.getDisplayValueForSubmitValue(Boolean.toString(energyOffer.isSlope()));
        }

    };

    useSlopeColumn.setSortable(true);
    sortHandler.setComparator(useSlopeColumn, new Comparator<EnergyOfferDTO>() {
        @Override
        public int compare(EnergyOfferDTO eo1, EnergyOfferDTO eo2) {
            return eo1.getDateTime().compareTo(eo2.getDateTime());
        }
    });

    addColumn(useSlopeColumn, UiMessages.INSTANCE.use_slope());
    setColumnWidth(useSlopeColumn, 10, Unit.PCT);
    setColumnHorizontalAlignment(useSlopeColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}

protected void addPriceMwColumn(final int colIndex, DisplayMode currentDisplayMode) {

    // Construct a composite cell for energy offers that includes a pair of text inputs
    final List<HasCell<EnergyOfferDTO, ?>> hasCells = new ArrayList<
            HasCell<EnergyOfferDTO, ?>>();

    // this DTO is passed along so that price and mw values for new entries are kept together
    final OfferPriceMwPairDTO newOfferPriceMwPairDTO = new OfferPriceMwPairDTO();

    // Price
    final HasCell<EnergyOfferDTO, String> priceCell = generatePriceCell(colIndex, newOfferPriceMwPairDTO, currentDisplayMode);
    hasCells.add(priceCell);

    // MW
    final HasCell<EnergyOfferDTO, String> mwCell = generateMwCell(colIndex, newOfferPriceMwPairDTO, currentDisplayMode);
    hasCells.add(mwCell);

    // Composite
    final CompositeCell<EnergyOfferDTO> priceMwCell = generateCompositeCell(hasCells);

    final Column<EnergyOfferDTO, EnergyOfferDTO> priceMwColumn = new Column<EnergyOfferDTO, EnergyOfferDTO>(priceMwCell) {

        @Override
        public EnergyOfferDTO getValue(EnergyOfferDTO energyOffer) {
            // we do this to satisfy the anonymous type's contract,
            // but know that this column's composite cell delegates to its individual cell impls to get a value
            return null;
        }

    };

    addColumn(priceMwColumn, String.valueOf(colIndex));
    setColumnWidth(priceMwColumn, 8, Unit.PCT);
    setColumnHorizontalAlignment(priceMwColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}

protected HasCell<EnergyOfferDTO, String> generatePriceCell(final int colIndex, final OfferPriceMwPairDTO newOfferPriceMwPair, DisplayMode currentDisplayMode) {
    HasCell<EnergyOfferDTO, String> priceCell;

    if (isInEditMode(currentDisplayMode)) {
        priceCell = new HasCell<EnergyOfferDTO, String>() {

            private TextInputCell cell = new TextInputCell();

            @Override
            public Cell<String> getCell() {
                return cell;
            }

            @Override
            public FieldUpdater<EnergyOfferDTO, String> getFieldUpdater() {
                return new FieldUpdater<EnergyOfferDTO, String>() {
                    @Override
                    public void update(int index, EnergyOfferDTO energyOffer, String value) {
                        if (value != null && !value.isEmpty()) {
                            // number format exceptions should be caught and handled by event bus's handle method
                            final double valueAsDouble = NumberFormat.getDecimalFormat().parse(value);

                            final BigDecimal price = BigDecimal.valueOf(valueAsDouble);
                            final List<OfferPriceMwPairDTO> offerPriceCurve = energyOffer.getOfferPriceCurve();
                            final OfferPriceMwPairDTO offerPriceMwPairDTO = offerPriceCurve.get(colIndex);
                            if (offerPriceMwPairDTO == null) {  // we have a new price value
                                newOfferPriceMwPair.setPrice(price);
                                offerPriceCurve.add(newOfferPriceMwPair);
                            } else {
                                offerPriceMwPairDTO.setPrice(price);
                            }

                        }
                    }
                };
            }

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                String result = "";
                if (energyOffer != null) {
                    final List<OfferPriceMwPairDTO> offerPriceCurve = energyOffer.getOfferPriceCurve();
                    final OfferPriceMwPairDTO offerPriceMwPairDTO = offerPriceCurve.get(colIndex);
                    if (offerPriceMwPairDTO != null) {
                        final BigDecimal price = offerPriceMwPairDTO.getPrice();
                        result = String.valueOf(price.doubleValue());
                    }
                }
                return result;
            }
        };
    } else {
        priceCell = new Column<EnergyOfferDTO, String>(new TextCell()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                String result = "";
                if (energyOffer != null) {
                    final List<OfferPriceMwPairDTO> offerPriceCurve = energyOffer.getOfferPriceCurve();
                    final OfferPriceMwPairDTO offerPriceMwPairDTO = offerPriceCurve.get(colIndex);
                    if (offerPriceMwPairDTO != null) {
                        final BigDecimal price = offerPriceMwPairDTO.getPrice();
                        result = String.valueOf(price.doubleValue());
                    }
                }
                return result;
            }
        };
    }
    return priceCell;
}

protected HasCell<EnergyOfferDTO, String> generateMwCell(final int colIndex, final OfferPriceMwPairDTO newOfferPriceMwPair, DisplayMode currentDisplayMode) {
    HasCell<EnergyOfferDTO, String> mwCell;

    if (isInEditMode(currentDisplayMode)) {
        mwCell = new HasCell<EnergyOfferDTO, String>() {

            private TextInputCell cell = new TextInputCell();

            @Override
            public Cell<String> getCell() {
                return cell;
            }

            @Override
            public FieldUpdater<EnergyOfferDTO, String> getFieldUpdater() {
                return new FieldUpdater<EnergyOfferDTO, String>() {
                    @Override
                    public void update(int index, EnergyOfferDTO energyOffer, String value) {
                        if (value != null && !value.isEmpty()) {
                            // number format exceptions should be caught and handled by event bus's handle method
                            final double valueAsDouble = NumberFormat.getDecimalFormat().parse(value);

                            final BigDecimal mw = BigDecimal.valueOf(valueAsDouble);
                            final List<OfferPriceMwPairDTO> offerPriceCurve = energyOffer.getOfferPriceCurve();
                            final OfferPriceMwPairDTO offerPriceMwPairDTO = offerPriceCurve.get(colIndex);
                            if (offerPriceMwPairDTO == null) {  // we have a new mw value
                                newOfferPriceMwPair.setMw(mw);
                                offerPriceCurve.add(newOfferPriceMwPair);
                            } else {
                                offerPriceMwPairDTO.setMw(mw);
                            }

                        }
                    }
                };
            }

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                String result = "";
                if (energyOffer != null) {
                    final List<OfferPriceMwPairDTO> offerPriceCurve = energyOffer.getOfferPriceCurve();
                    final OfferPriceMwPairDTO offerPriceMwPairDTO = offerPriceCurve.get(colIndex);
                    if (offerPriceMwPairDTO != null) {
                        final BigDecimal mw = offerPriceMwPairDTO.getMw();
                        result = String.valueOf(mw.doubleValue());
                    }
                }
                return result;
            }
        };
    } else {
        mwCell = new Column<EnergyOfferDTO, String>(new TextCell()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                String result = "";
                if (energyOffer != null) {
                    final List<OfferPriceMwPairDTO> offerPriceCurve = energyOffer.getOfferPriceCurve();
                    final OfferPriceMwPairDTO offerPriceMwPairDTO = offerPriceCurve.get(colIndex);
                    if (offerPriceMwPairDTO != null) {
                        final BigDecimal mw = offerPriceMwPairDTO.getMw();
                        result = String.valueOf(mw.doubleValue());
                    }
                }
                return result;
            }
        };
    }
    return mwCell;
}

protected CompositeCell<EnergyOfferDTO> generateCompositeCell(final List<HasCell<EnergyOfferDTO, ?>> hasCells) {
    final CompositeCell<EnergyOfferDTO> compositeCell = new CompositeCell<EnergyOfferDTO>(hasCells) {

        @Override
        public void render(Context context, EnergyOfferDTO value, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<table><tbody><tr>");
            for (final HasCell<EnergyOfferDTO, ?> hasCell : hasCells) {
                render(context, value, sb, hasCell);
            }
            sb.appendHtmlConstant("</tr></tbody></table>");
        }

        @Override
        protected Element getContainerElement(Element parent) {
            // Return the first TR element in the table.
            return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
        }

        @Override
        protected <X> void render(Context context, EnergyOfferDTO value,
                SafeHtmlBuilder sb, HasCell<EnergyOfferDTO, X> hasCell) {
            final Cell<X> cell = hasCell.getCell();
            sb.appendHtmlConstant("<td>");
            cell.render(context, hasCell.getValue(value), sb);
            sb.appendHtmlConstant("</td>");
        }
    };
    return compositeCell;
}

}

I have an energy offer (EnergyOfferDTO). It has a list of MW/Price points (OfferPriceMWPairDTO). I want to render a grid where for hours of a day I can see up to 10 curves (curves being the collection of MW/Price points for the day). I want each of these curve columns to contain a pair of input fields (one for price and one for mw value). I figure, hey, create columns and cells for each, then bring them together in a CompositeCell. How hard could that be?

I decided to extend CellTable (i.e., ToggleableGrid) so that I could encapsulate boiler-plate setup, style and behavior; as well to set a display mode. The mode is consulted as columns are being built (see isInEditMode) to render either a TextCell or a concrete derivative of AbstractInputCell, like TextInputCell. I also created an extension for SelectionCell (i.e., ReferenceDataBackedSelectionCell) so that I could set the value of an option using ReferenceData. Single input columns work! I can display them as text or as input field or select list. It's the CompositeCell that's causing me headaches.

While this code will render pairs of input fields correctly, values are all blank (null), either blank text or blank input field pairs.

Please have a gander at the addPriceMwColumn method. Maybe you can see something I don't?

Jaco answered 1/2, 2012 at 5:51 Comment(0)
T
2

Could it be as simple as returning the EnergyOfferDTO in your column instead of null? (you might want to use an IdentityColumn)

You say in a comment in your code that the composite cell will delegate to its cells, but it's the responsibility of the column to give the value to the composite cell in the first place, then the composite cell will call each HasCell to extract the inner cell's value from the value passed to the composite cell.

As a side note, your composite cell's render method shouldn't loop over the hasCells list, it should simply call super.render(context, value, sb) (of course still decorating it with your appendHtmlConstant calls) which will do the job.

Tinkling answered 1/2, 2012 at 10:6 Comment(2)
Oops! In addPriceMwColumn(int, DisplayMode), as you suggested I opted to construct an IndentityColumn which returns the energyOffer. In generateCompositeCell(List), I made the changes in render logic. And then I had to update the for loop in initializeStructure to start with 0, i.e., int i = 0; i < MAX_NUMBER_OF_MW_PRICE_POINTS; i++), and in generatePriceCell and generateMwCell I had to adjust logic to check that colIndex does not cause an IndexOutOfBoundsException. Thanks Thomas! I now have a working ToggleableGrid!Jaco
Next up... a) introducing client-side validation and b) round-tripping updates to a database.Jaco

© 2022 - 2024 — McMap. All rights reserved.