Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/gwt/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Validation 在搜索GWT验证示例时。。。你在哪里?_Validation_Gwt_Celltable - Fatal编程技术网

Validation 在搜索GWT验证示例时。。。你在哪里?

Validation 在搜索GWT验证示例时。。。你在哪里?,validation,gwt,celltable,Validation,Gwt,Celltable,作为后续行动 我正在尝试添加JSR-303验证支持。我在这里遵循了Koma的配置建议:(注意:我使用的是GWT2.4的内置验证,而不是GWT验证) 同样,为了得到一些重用,我制作了一对类,ValidatableInputCell和AbstractValidatableColumn。我从以下方面获得灵感: 让我们看看他们 public class ValidatableInputCell extends AbstractInputCell<String, ValidatableInp

作为后续行动

我正在尝试添加JSR-303验证支持。我在这里遵循了Koma的配置建议:(注意:我使用的是GWT2.4的内置验证,而不是GWT验证)

同样,为了得到一些重用,我制作了一对类,ValidatableInputCellAbstractValidatableColumn。我从以下方面获得灵感:

让我们看看他们

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;
    }
}

}
如果在onBrowserEvent中删除断点,我希望在每个按键笔划和/或单元格失去焦点后都会触发验证触发器。它永远不会被调用。我可以随意进入牢房。关于修复方法有什么线索吗

我早期的想法。。。a) AbstractValidableColumn#getFieldUpdater从未被调用,b)ValidableInputCell#onBrowserEvent或ValidableInputCell#render中的逻辑需要大修


最后,我希望看到违反约束的每个单元格旁边都会出现一个弹出窗口,当然也希望看到应用了适当的颜色。

我不清楚为什么会从
generatePriceColumn
返回
HasCell
,因为实际上任何东西都不能使用它,除了
CompositeCell
——可能您正试图将所有这些都打包到一个更大的单元格中。在提问之前,你可以考虑在将来进一步细分你的例子,这个问题可能会变得清晰。

我更改了“column”创建代码,因此它实际上返回了一个列—这意味着将AbstractValidatableColumn更改为扩展列。在这个过程中,我注意到您正在重写getFieldUpdater,而没有修改底层字段,这将阻止列的其他内部部分在查找该字段时工作。因此,我最初的实验是正确地使用
validableInputCell.onBrowserEvent
的keyup案例,但是没有
ValueUpdater
实例可供使用,因为
FieldUpdater
列中为空

在这一点上,我没有连接的验证逻辑正在被调用——从GWT 2.4.0开始,它仍然在每个类中被标记为“实验性”,并且不用于生产代码,因此我给它一个通行证,直到2.5.0左右,当粗糙的边缘被舍入时。如果我要继续(如果你有问题的话),我会从项目开始-让它工作,然后窃取细节,直到我的工作也一样

其他一些意见:

不要扩展类来添加功能,除非您希望/允许该类的任何使用者像使用子类一样使用它。在这种情况下很难说,但是
generatePriceColumn
似乎位于
CellTable
子类上,该子类

  • 允许任何使用它的代码更改celltable其余部分的设置方式
  • 实际上,它不像
    CellTable
    方法-其他关注列的方法实际上是添加列而不是返回列
  • 可能会将您锁定为始终使用
    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;
      }
    }
    
    公共抽象类AbstractValidatableColumn扩展列{
    公共AbstractValidatableColumn(int inputSize,最终AbstractCellTable表){
    super(新的ValidatableInputCell());
    ((ValidatableInputCell)getCell()).setInputSize(inputSize);
    setFieldUpdater(新的FieldUpdater(){
    公共void更新(int索引、T dto、字符串值){
    最终设置冲突=验证(dto);
    最终验证数据viewData=getCell().getViewData(dto);
    如果(!inflictions.isEmpty()){//无效
    final StringBuffer errorMessage=新StringBuffer();
    对于(最终约束强制约束强制约束:违反){
    errorMessage.append(constraintViolation.getMessage());
    }
    viewData.setInvalid(true);
    getCell().setErrorMessage(errorMessage.toString());
    表1.redraw();
    }else{//有效
    viewData.setInvalid(false);
    getCell().setErrorMessage(null);
    doUpdate(索引、dto、值);
    }
    }
    });
    }
    @凌驾
    public ValidatableInputCell getCell(){
    返回(ValidatableInputCell)super.getCell();
    }
    受保护的抽象void doUpdate(int索引、T dto、字符串值);
    受保护集验证(T dto){
    最终验证器验证器=Validation.buildDefaultValidatorFactory().getValidator();
    最终设置冲突=validator.validate(dto);
    返回违规行为;
    }
    }
    
    FieldUpdater是这里有趣的部分,这是应该重点关注的,并留下尽可能多的其他部分供重用。这将允许任何单元格在准备就绪时运行自己的ValueUpdater—可能没有您希望的那么频繁,但它通常会使事情更易于快速使用。制作一个FieldUpdater impl来包装另一个FieldUpdater,它可以特定于在这种情况下更改的任何字段

    我认为这里潜藏着另一个bug,如果您单独测试column/fieldupdater,可能会出现这个bug——直到验证运行之后,新值才应用于t类型的bean,因此正在使用旧的有效值验证bean<需要更快地调用code>doUpdate

    最后,我鼓励大家在学习的过程中尽量简化自己的例子——有些夸张
    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;
    }
    
    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;
    
    ...
    
    }
    
    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;
      }
    }
    
    /**
     * 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;
        }
    }
    
    }
    
    /**
     * 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;
    }
    }
    
    /**
     * 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;
                }
    
            };