В поисках примера подтверждения GWT ... где ты? - PullRequest
11 голосов
/ 03 февраля 2012

В качестве дополнения к Почему в CellTable нет достойных примеров использования CompositeCell?

Я пытаюсь добавить поддержку проверки JSR-303.Я следовал советам по настройке Koma здесь: Как установить gwt-validation с gwt-2.4.0 (Примечание: я использую встроенную проверку GWT 2.4, а не GWT-Validation).

Опять же, для повторного использования я создал пару классов: ValidatableInputCell и AbstractValidatableColumn .Я получил вдохновение для них от:

Давайте посмотрим на них...

public class ValidatableInputCell extends AbstractInputCell<String, ValidatableInputCell.ValidationData> {

interface Template extends SafeHtmlTemplates {
    @Template("<input type=\"text\" value=\"{0}\" size=\"{1}\" style=\"{2}\" tabindex=\"-1\"></input>")
    SafeHtml input(String value, String width, SafeStyles color);
}

private static Template template;

/**
 * The error message to be displayed as a pop-up near the field
 */
private String errorMessage;

private static final int DEFAULT_INPUT_SIZE = 15;

/**
 * Specifies the width, in characters, of the &lt;input&gt; element contained within this cell
 */
private int inputSize = DEFAULT_INPUT_SIZE;

public ValidatableInputCell() {
    super("change", "keyup");
    if (template == null) {
        template = GWT.create(Template.class);
    }
}

public void setInputSize(int inputSize) {
    this.inputSize = inputSize;
}

public void setErrorMessage(String errorMessage) {
    this.errorMessage = SafeHtmlUtils.htmlEscape(errorMessage);
}

@Override
public void onBrowserEvent(Context context, Element parent, String value,
        NativeEvent event, ValueUpdater<String> valueUpdater) {
    super.onBrowserEvent(context, parent, value, event, valueUpdater);

    // Ignore events that don't target the input.
    final InputElement input = (InputElement) getInputElement(parent);
    final Element target = event.getEventTarget().cast();
    if (!input.isOrHasChild(target)) {
        return;
    }

    final Object key = context.getKey();
    final String eventType = event.getType();

    if ("change".equals(eventType)) {
        finishEditing(parent, value, key, valueUpdater);
    } else if ("keyup".equals(eventType)) {
        // Mark cell as containing a pending change
        input.getStyle().setColor("blue");

        ValidationData viewData = getViewData(key);
        // Save the new value in the view data.
        if (viewData == null) {
            viewData = new ValidationData();
            setViewData(key, viewData);
        }
        final String newValue = input.getValue();
        viewData.setValue(newValue);
        finishEditing(parent, newValue, key, valueUpdater);

        // Update the value updater, which updates the field updater.
        if (valueUpdater != null) {
            valueUpdater.update(newValue);
        }
    }
}

@Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
    // Get the view data.
    final Object key = context.getKey();
    ValidationData viewData = getViewData(key);
    if (viewData != null && viewData.getValue().equals(value)) {
        // Clear the view data if the value is the same as the current value.
        clearViewData(key);
        viewData = null;
    }

    /*
     * If viewData is null, just paint the contents black. If it is non-null,
     * show the pending value and paint the contents red if they are known to
     * be invalid.
     */
    final String pendingValue = viewData == null ? null : viewData.getValue();
    final boolean invalid = viewData == null ? false : viewData.isInvalid();

    final String color = pendingValue != null ? invalid ? "red" : "blue" : "black";
    final SafeStyles safeColor = SafeStylesUtils.fromTrustedString("color: " + color + ";");
    sb.append(template.input(pendingValue != null ? pendingValue : value, String.valueOf(inputSize), safeColor));
}

@Override
protected void onEnterKeyDown(Context context, Element parent, String value,
        NativeEvent event, ValueUpdater<String> valueUpdater) {
    final Element target = event.getEventTarget().cast();
    if (getInputElement(parent).isOrHasChild(target)) {
        finishEditing(parent, value, context.getKey(), valueUpdater);
    } else {
        super.onEnterKeyDown(context, parent, value, event, valueUpdater);
    }
}

@Override
protected void finishEditing(Element parent, String value, Object key,
        ValueUpdater<String> valueUpdater) {
    final ValidationData viewData = getViewData(key);

    final String pendingValue = viewData == null ? null : viewData.getValue();
    final boolean invalid = viewData == null ? false : viewData.isInvalid();

    if (invalid) {
        final DecoratedPopupPanel errorMessagePopup = new DecoratedPopupPanel(true);
        final VerticalPanel messageContainer = new VerticalPanel();
        messageContainer.setWidth("200px");
        final Label messageTxt = new Label(errorMessage, true);
        messageTxt.setStyleName(UiResources.INSTANCE.style().error());
        messageContainer.add(messageTxt);
        errorMessagePopup.setWidget(messageContainer);

        // Reposition the popup relative to input field
        final int left = parent.getAbsoluteRight() + 25;
        final int top = parent.getAbsoluteTop();

        errorMessagePopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
            @Override
            public void setPosition(int offsetWidth, int offsetHeight) {
                errorMessagePopup.setPopupPosition(left, top);
            }
        });
    }
    // XXX let user continue or force focus until value is valid? for now the former is implemented
    super.finishEditing(parent, pendingValue, key, valueUpdater);
}

/**
 * The ViewData used by {@link ValidatableInputCell}.
 */
static class ValidationData {
    private boolean invalid;
    private String value;

    public String getValue() {
        return value;
    }

    public boolean isInvalid() {
        return invalid;
    }

    public void setInvalid(boolean invalid) {
        this.invalid = invalid;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

}

и

public abstract class AbstractValidatableColumn<T> implements HasCell<T, String> {

private ValidatableInputCell cell = new ValidatableInputCell();
private CellTable<T> table;

public AbstractValidatableColumn(int inputSize, CellTable<T> table) {
    cell.setInputSize(inputSize);
    this.table = table;
}

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

@Override
public FieldUpdater<T, String> getFieldUpdater() {
    return new FieldUpdater<T, String>() {
        @Override
        public void update(int index, T dto, String value) {
            final Set<ConstraintViolation<T>> violations = validate(dto);
            final ValidationData viewData = cell.getViewData(dto);
            if (!violations.isEmpty()) {  // invalid
                final StringBuffer errorMessage = new StringBuffer();
                for (final ConstraintViolation<T> constraintViolation : violations) {
                    errorMessage.append(constraintViolation.getMessage());
                }
                viewData.setInvalid(true);
                cell.setErrorMessage(errorMessage.toString());
                table.redraw();
            } else {  // valid
                viewData.setInvalid(false);
                cell.setErrorMessage(null);
                doUpdate(index, dto, value);
            }
        }
    };
}

protected abstract void doUpdate(int index, T dto, String value);

protected Set<ConstraintViolation<T>> validate(T dto) {
    final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    final Set<ConstraintViolation<T>> violations = validator.validate(dto);
    return violations;
}

}

Я использую AbstractValidatableColumn примерно так ...

protected HasCell<ReserveOfferDTO, String> generatePriceColumn(DisplayMode currentDisplayMode) {
    HasCell<ReserveOfferDTO, String> priceColumn;
    if (isInEditMode(currentDisplayMode)) {
        priceColumn = new AbstractValidatableColumn<ReserveOfferDTO>(5, this) {

            @Override
            public String getValue(ReserveOfferDTO reserveOffer) {
                return obtainPriceValue(reserveOffer);
            }

            @Override
            protected void doUpdate(int index, ReserveOfferDTO reserveOffer, String value) {
                // 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);
                reserveOffer.setPrice(price);
            }

        };
    } else {
        priceColumn = new Column<ReserveOfferDTO, String>(new TextCell()) {

            @Override
            public String getValue(ReserveOfferDTO reserveOffer) {
                return obtainPriceValue(reserveOffer);
            }
        };
    }
    return priceColumn;
}

О!А вот DTO с аннотациями JSR-303 ...

public class ReserveOfferDTO extends DateComparable implements Serializable {

private static final long serialVersionUID = 1L;

@NotNull @Digits(integer=6, fraction=2)
private BigDecimal price;
@NotNull @Digits(integer=6, fraction=2)
private BigDecimal fixedMW;

private String dispatchStatus;
private String resourceName;
private String dateTime;
private String marketType;
private String productType;

...

}

Удаление точки останова в onBrowserEvent Я ожидаю, что при каждом нажатии клавиши будет запускаться валидатори / или после того, как клетка теряет фокус.Это никогда не вызывается.Я могу ввести в камеру все, что захочу.Какие-нибудь подсказки относительно подхода к исправлению?

Мои первые мысли ... а) AbstractValidatableColumn # getFieldUpdater никогда не вызывается и б) логика в ValidatableInputCell # onBrowserEvent или ValidatableInputCell # нуждается в капитальном ремонте.

В конечном счете, я хотел бы видеть всплывающее окно рядом с каждой ячейкой, которое нарушает ограничение, и, конечно, видеть, что применяется соответствующая окраска.

Ответы [ 2 ]

3 голосов
/ 16 мая 2012

Подойди сюда. Я наконец-то нашел решение! Я решил использовать библиотеку GWT Validation , см. http://code.google.com/p/gwt-validation/wiki/GWT_Validation_2_0 (код ниже, как известно, работает с 2.1 SNAPSHOT).

Хитрость при выполнении проверки для ячейки заключается в вызове validateValue вместо проверки (последний запускает проверку для всех полей сущности).Кроме того, все значения входных ячеек являются String и преобразуются в соответствующий тип поля сущности перед проверкой.(Даже работает для вложенных полей сущностей).

Вот пересмотренные имплсы для AbstractValidatableColumn (AVC) и ValidatableInputCell .

/**
 * A {@link Column} implementation that encapsulates a {@link ValidatableInputCell}.
 * Performs JSR-303 validation on a field (or nested field) of the type.
 * @author cphillipson
 *
 * @param <T> the type
 * @param <O> the owning type of the field to be validated; in many cases T may have only primitive or wrapper types, therefore O will be the same type as T
 */
public abstract class AbstractValidatableColumn<T, O> extends Column<T, String> {

/**
 * Preferred constructor.
 * Allows for definition of tabIndex but uses a default for the input cell size.
 * @param tabIndex the <code>tabindex</code> attribute's value for the input cell
 * @param table the grid instance
 */
public AbstractValidatableColumn(int tabIndex, final AbstractHasData<T> table) {
    this(App.INSTANCE.defaultValidatableInputCellSize(), tabIndex, table);
}

/**
 * Overloaded constructor.
 * Allows for definition of tabIndex and allows for an override to the default for the input cell size.
 * @param inputSize the <code>size</code> attribute's value for the input cell
 * @param tabIndex the <code>tabindex</code> attribute's value for the input cell
 * @param table the grid instance
 */
public AbstractValidatableColumn(int inputSize, int tabIndex, final AbstractHasData<T> table) {
    super(new ValidatableInputCell());
    getCell().setInputSize(inputSize);
    getCell().setTabIndex(tabIndex);
    init(table);
}

// meat and potatoes
private void init(final AbstractHasData<T> table) {
    setFieldUpdater(new FieldUpdater<T, String>() {
        @Override
        public void update(int index, T dto, String newValue) {
            final ConversionResult cr = attemptValueConversion(newValue);
            final ValidationData viewData = getCell().getViewData(dto);
            if (cr.wasConvertedSuccessfully()) {
                final Set<ConstraintViolation<O>> violations = validate(cr.getValue());
                if (!violations.isEmpty()) {  // invalid
                    final StringBuffer errorMessage = new StringBuffer();
                    for (final ConstraintViolation<O> constraintViolation : violations) {
                        errorMessage.append(constraintViolation.getMessage());
                    }
                    viewData.setInvalid(true);
                    getCell().setErrorMessage(errorMessage.toString());
                } else {  // valid
                    viewData.setInvalid(false);
                    getCell().setErrorMessage("");
                    doUpdate(index, dto, newValue);
                }
            } else { // conversion exception
                viewData.setInvalid(true);
                getCell().setErrorMessage(UiMessages.INSTANCE.improper_input_format());
            }
        }
    });
}


/**
 * Attempts conversion of a String value into another type
 * Instances are responsible for the conversion logic as it may vary from type to type
 * @param value a String value to be converted into an owning class's property type
 * @return a ConversionResult
 */
protected abstract ConversionResult attemptValueConversion(String value);

@Override
public ValidatableInputCell getCell() {
    return (ValidatableInputCell) super.getCell();
}

/**
 * Template method for updating a field (or nested field) value within a DTO
 * @param index the row index for the instance of the DTO within the grid
 * @param dto the object whose field we wish to update
 * @param value the new value that will be set on a field (or nested field) of the DTO
 */
protected abstract void doUpdate(int index, T dto, String value);

/**
 * Template method for specifying the property name of an owning class
 * @return the field name of the owning class whose value is to be updated
 */
protected abstract String getPropertyName();

/**
 * Template method for specifying the owning class
 * @return the owning class of the field whose value is to be updated
 */
protected abstract Class<O> getPropertyOwner();

/**
 * Validates a value against a set of constraints (i.e., JSR-303 annotations on a field)
 * @param newValue the value to be validated
 * @return the set of constraint violations induced by an inappropriate value
 */
protected Set<ConstraintViolation<O>> validate(Object newValue) {
    final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    final Set<ConstraintViolation<O>> violations = validator.validateValue(getPropertyOwner(), getPropertyName(), newValue);
    return violations;
}

}

/**
 * <p>A cell that will update its styling and provide feedback upon a validation constraint violation.</p>
 * <p>Implementation based upon GWT Showcase's <a href="http://gwt.google.com/samples/Showcase/Showcase.html#!CwCellValidation">Cell Validation</a> example.</p>
 * @author cphillipson
 *
 */
 public class ValidatableInputCell extends AbstractInputCell<String, ValidatableInputCell.ValidationData> {

interface Template extends SafeHtmlTemplates {
    @Template("<input type=\"text\" value=\"{0}\" size=\"{1}\" style=\"{2}\" tabindex=\"{3}\"></input>")
    SafeHtml input(String value, String width, SafeStyles color, String tabIndex);
}

private static Template template;

/**
 * The error message to be displayed as a pop-up near the field
 */
private String errorMessage;

private static final int DEFAULT_INPUT_SIZE = App.INSTANCE.defaultValidatableInputCellSize();

/**
 * Specifies the width, in characters, of the &lt;input&gt; element contained within this cell
 */
private int inputSize = DEFAULT_INPUT_SIZE;

/**
 * Specifies the tab index for this cell
 */
private int tabIndex = -1;

public ValidatableInputCell() {
    // since onBrowserEvent method is overridden, we must register all events that handled in overridden method impl
    super("change", "keyup", "focus", "blur", "keydown");
    if (template == null) {
        template = GWT.create(Template.class);
    }
}

public void setInputSize(int inputSize) {
    this.inputSize = inputSize;
}

public void setTabIndex(int index) {
    tabIndex = index;
}

public void setErrorMessage(String errorMessage) {
    this.errorMessage = SafeHtmlUtils.fromSafeConstant(errorMessage).asString();
}

@Override
public void onBrowserEvent(Context context, Element parent, String value,
        NativeEvent event, ValueUpdater<String> valueUpdater) {
    super.onBrowserEvent(context, parent, value, event, valueUpdater);

    final InputElement input = (InputElement) getInputElement(parent);
    final Object key = context.getKey();
    final String eventType = event.getType();

    if ("keyup".equals(eventType)) {

        ValidationData viewData = getViewData(key);
        // Save the new value in the view data.
        if (viewData == null) {
            viewData = new ValidationData();
            setViewData(key, viewData);
        }
        final String newValue = input.getValue();
        viewData.setValue(newValue);

        finishEditing(parent, newValue, key, valueUpdater);
    }
}

@Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
    // Get the view data.
    final Object key = context.getKey();
    ValidationData viewData = getViewData(key);
    if (viewData != null && viewData.getValue().equals(value)) {
        // Clear the view data if the value is the same as the current value.
        clearViewData(key);
        viewData = null;
    }

    /*
     * If viewData is null, just paint the contents black. If it is non-null,
     * show the pending value and paint the contents red if they are known to
     * be invalid.
     */
    final String pendingValue = viewData == null ? null : viewData.getValue();
    final boolean invalid = viewData == null ? false : viewData.isInvalid();

    final String color = pendingValue != null ? invalid ? App.INSTANCE.invalidCellInputTextColor() : App.INSTANCE.pendingCellInputTextColor() : App.INSTANCE.defaultCellInputTextColor();
    final String backgroundColor = pendingValue != null ? invalid ? App.INSTANCE.invalidCellInputTextBackgroundColor() : App.INSTANCE.pendingCellInputTextBackgroundColor() : App.INSTANCE.defaultCellInputTextBackgroundColor();
    final SafeStyles style = SafeStylesUtils.fromTrustedString("color: " + color + "; background-color: " + backgroundColor + ";");
    sb.append(template.input(pendingValue != null ? pendingValue : value, String.valueOf(inputSize), style, String.valueOf(tabIndex)));
}

/*
@Override
protected void onEnterKeyDown(Context context, Element parent, String value,
        NativeEvent event, ValueUpdater<String> valueUpdater) {
    final Element target = event.getEventTarget().cast();
    if (getInputElement(parent).isOrHasChild(target)) {
        finishEditing(parent, value, context.getKey(), valueUpdater);
    } else {
        super.onEnterKeyDown(context, parent, value, event, valueUpdater);
    }
}
 */

@Override
protected void onEnterKeyDown(Context context, Element parent, String value,
        NativeEvent event, ValueUpdater<String> valueUpdater) {
    // do nothing
}

@Override
protected void finishEditing(Element parent, String value, Object key,
        ValueUpdater<String> valueUpdater) {

    // Update the value updater, which updates the field updater.
    if (valueUpdater != null) {
        valueUpdater.update(value);
    }

    final InputElement input = (InputElement) getInputElement(parent);
    final ValidationData viewData = getViewData(key);

    /*
     * If viewData is null, just paint the contents black. If it is non-null,
     * show the pending value and paint the contents red if they are known to
     * be invalid.
     */
    final String pendingValue = viewData == null ? null : viewData.getValue();
    final boolean invalid = viewData == null ? false : viewData.isInvalid();

    final String color = pendingValue != null ? invalid ? App.INSTANCE.invalidCellInputTextColor() : App.INSTANCE.pendingCellInputTextColor() : App.INSTANCE.defaultCellInputTextColor();
    final String backgroundColor = pendingValue != null ? invalid ? App.INSTANCE.invalidCellInputTextBackgroundColor() : App.INSTANCE.pendingCellInputTextBackgroundColor() : App.INSTANCE.defaultCellInputTextBackgroundColor();
    input.getStyle().setColor(color);
    input.getStyle().setBackgroundColor(backgroundColor);

    if (invalid) {
        final DecoratedPopupPanel errorMessagePopup = new DecoratedPopupPanel(true);
        final FlowPanel messageContainer = new FlowPanel();
        messageContainer.setWidth(App.INSTANCE.errorMessagePopupWidth());
        final Label messageTxt = new Label(errorMessage, true);
        messageTxt.setStyleName(UiResources.INSTANCE.style().error());
        messageContainer.add(messageTxt);
        errorMessagePopup.setWidget(messageContainer);

        // Reposition the popup relative to input field
        final int left = parent.getAbsoluteRight() +5;
        final int top = parent.getAbsoluteTop() - 5;

        errorMessagePopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
            @Override
            public void setPosition(int offsetWidth, int offsetHeight) {
                errorMessagePopup.setPopupPosition(left, top);
            }
        });
    }

}

/**
 * The ViewData used by {@link ValidatableInputCell}.
 */
static class ValidationData {
    private boolean invalid;
    private String value;

    public String getValue() {
        return value;
    }

    public boolean isInvalid() {
        return invalid;
    }

    public void setInvalid(boolean invalid) {
        this.invalid = invalid;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

}

ВариантыAVC может выглядеть так ...

/**
 * A variant of {@link AbstractValidatableColumn} that works with {@link BigDecimal} field types.
 * @author cphillipson
 *
 * @param <T> the type
 * @param <O> the owning type of the field to be validated; in many cases T may have only primitive or wrapper types, therefore O will be the same type as T
 */
public abstract class BigDecimalValidatableColumn<T, O> extends AbstractValidatableColumn<T, O> {

public BigDecimalValidatableColumn(int tabIndex, AbstractHasData table) {
    super(tabIndex, table);
}

public BigDecimalValidatableColumn(int inputSize, int tabIndex, final AbstractHasData<T> table) {
    super(inputSize, tabIndex, table);
}

@Override
protected ConversionResult attemptValueConversion(String value) {
    return doConversion(value);
}

public static ConversionResult doConversion(String value) {
    ConversionResult result = null;
    try {
        final Double dblValue = Double.valueOf(value);
        final BigDecimal convertedValue = BigDecimal.valueOf(dblValue);
        result = ConversionResult.converted(convertedValue);
    } catch (final NumberFormatException nfe) {
        result = ConversionResult.not_converted();
    }
    return result;
}
}

A ConversionResult запрашивается FieldUpdater столбца.Вот как это выглядит ...

/**
 * An attempted conversion result.
 * Returns both the converted value (from <code>String</code>) and whether or not the conversion was successful.
 * E.g., if you tried to convert from a <code>String</code> to a <code>Number</code>, in the failure case this would result in a <code>NumberFormatException</code>.
 * On failure, the boolean would be false and the value would be null.
 * On success, the boolean would be true and the value would be of the type needed to continue validation against a set of constraints
 * @author cphillipson
 *
 */
public  class ConversionResult {
private Object value;
private boolean convertedSuccessfully;

private ConversionResult () {}

/**
 * Use this method when a successful conversion is made to return a result
 * @param value the convertedValue
 * @return the result of the conversion containing the converted value and a success flag
 */
public static ConversionResult converted(Object value) {
    final ConversionResult result = new ConversionResult();
    result.setConvertedSuccessfully(true);
    result.setValue(value);
    return result;
}

/**
 * Use this method when an attempt to convert a String value failed
 * @return the result of a failed conversion
 */
public static ConversionResult not_converted() {
    return new ConversionResult();
}

private void setValue(Object value) {
    this.value = value;
}

public Object getValue() {
    return value;
}

private void setConvertedSuccessfully(boolean flag) {
    convertedSuccessfully = flag;
}

public boolean wasConvertedSuccessfully() {
    return convertedSuccessfully;
}
}

Наконец, вот как вы можете указать столбец в сетке

new BigDecimalValidatableColumn<EnergyOfferDTO, OfferPriceMwPairDTO>(nextTabIndex(), getGrid()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                return obtainPriceValue(colIndex, energyOffer, false);
            }

            @Override
            public void doUpdate(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.getCurve();
                    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
            protected String getPropertyName() {
                return "price";
            }

            @Override
            protected Class<OfferPriceMwPairDTO> getPropertyOwner() {
                return OfferPriceMwPairDTO.class;
            }

        };

Обратите внимание, что DTO в приведенном выше примере имеют свои поля JSR-Ограничение 303 аннотировано (например, с @Digits, @NotNull).

Вышеуказанное потребовало некоторого усилия, и это может быть просто наиболее полным решением в сети на данный момент.Наслаждайтесь!

1 голос
/ 27 февраля 2012

Мне не понятно, почему HasCell возвращается generatePriceColumn, поскольку это не может быть использовано практически ничем, кроме CompositeCell - возможно, вы пытаетесь свернуть все это в большая клетка. Прежде чем спрашивать, вы можете подумать о том, чтобы в дальнейшем разбить ваш пример, проблема может стать ясной.

Я изменил код создания «столбца», чтобы он на самом деле возвращал столбец - это означало изменение AbstractValidatableColumn для расширения столбца. Попутно я заметил, что вы переопределяете getFieldUpdater, не изменяя базовое поле, что будет препятствовать работе других частей внутренних компонентов Column при их поиске в этом поле. В результате мои первоначальные эксперименты были правильно получены в случае с ключом ValidatableInputCell.onBrowserEvent, но не было экземпляра ValueUpdater, с которым можно было бы работать, так как FieldUpdater был нулевым в столбце.

В этот момент вызывается логика проверки, которую я не подключил. Начиная с GWT 2.4.0, она по-прежнему помечена в каждом классе как «ЭКСПЕРИМЕНТАЛЬНАЯ», а не для использования в производственном коде. Таким образом, я дал ему проход до 2.5.0 или около того, когда грубые края были округлены. Хотя, если бы я продолжил (и если у вас возникли проблемы), я бы начал с проекта http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/validation/ - заставил бы это работать, а затем украл детали, пока мой не заработал.

Несколько других наблюдений:

Не расширяйте классы для добавления функциональности, за исключением тех случаев, когда вы ожидаете / позволяете любому потребителю этого класса использовать его так же, как и подкласс. Трудно сказать в этом случае, но, похоже, generatePriceColumn относится к подклассу CellTable, который

  1. Позволяет любому коду, использующему его, изменять настройку остальной части таблицы ячеек,
  2. На самом деле не действует как CellTable метод - другие методы, ориентированные на столбцы, фактически добавляют столбец вместо его возврата
  3. Может заблокировать вас, чтобы всегда использовать CellTable (поскольку это то, что вы подкласс), в то время как этот метод будет работать отлично, в противном случае с AbstractCellTable подклассами, такими как DataTable, более новый CellTable

В этом случае я бы либо изменил метод на addPriceColumn(...), чтобы он использовал столбец, и добавил его в список, либо оставил его как столбец с подклассами или полностью сам по себе, как служебный метод. У моего последнего AbstractValidationColumn не было особых причин быть подклассом вообще, просто конструктор удобства для Column:

public abstract class AbstractValidatableColumn<T> extends Column<T, String> {

  public AbstractValidatableColumn(int inputSize, final AbstractCellTable<T> table) {
    super(new ValidatableInputCell());
    ((ValidatableInputCell) getCell()).setInputSize(inputSize);

    setFieldUpdater(new FieldUpdater<T, String>() {
      public void update(int index, T dto, String value) {
        final Set<ConstraintViolation<T>> violations = validate(dto);
        final ValidationData viewData = getCell().getViewData(dto);
        if (!violations.isEmpty()) {  // invalid
          final StringBuffer errorMessage = new StringBuffer();
          for (final ConstraintViolation<T> constraintViolation : violations) {
            errorMessage.append(constraintViolation.getMessage());
          }
          viewData.setInvalid(true);
          getCell().setErrorMessage(errorMessage.toString());
          table.redraw();
        } else {  // valid
          viewData.setInvalid(false);
          getCell().setErrorMessage(null);
          doUpdate(index, dto, value);
        }
      }
    });
  }

  @Override
  public ValidatableInputCell getCell() {
    return (ValidatableInputCell)super.getCell();
  }

  protected abstract void doUpdate(int index, T dto, String value);

  protected Set<ConstraintViolation<T>> validate(T dto) {
    final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    final Set<ConstraintViolation<T>> violations = validator.validate(dto);
    return violations;
  }
}

FieldUpdater - интересная часть, на которой следует сосредоточиться, и оставьте как можно больше других частей для повторного использования. Это позволит любой ячейке запускать свой собственный ValueUpdater, когда он будет готов - возможно, не так часто, как вам хотелось бы, но, как правило, упростит использование вещей быстрее. Создайте атрибут FieldUpdater, который оборачивает другой FieldUpdater, который может быть специфичным для любого поля, которое изменяется в этом случае.

Я думаю, что здесь скрывается еще одна ошибка, которая может появиться, если вы самостоятельно протестируете column / fieldupdater - новое значение не будет применено к бину типа T до тех пор, пока проверка не будет запущена, поэтому бин проверяется со старым допустимым значением. doUpdate нужно позвонить раньше.

И, наконец, я бы посоветовал вам упростить ваш пример по ходу дела - что-то вроде «мертвого мозга» для проверки на правильность, а простая простая настройка CellTable позволит вам увидеть, что сам столбец имеет только проверка работоспособности, если поле Column.fieldUpdater не равно нулю. Создайте из более простой конфигурации, которая работает, так что только одна вещь может пойти не так на каждом этапе.

...