正在寻找 GWT 验证示例...你在哪里?
作为 的后续内容为什么在 CellTable 中没有使用 CompositeCell 的合适示例?
我正在尝试添加 JSR-303 验证支持。我在这里遵循了 Koma 的配置建议:如何安装 gwt -使用gwt-2.4.0进行验证(注意:我使用的是GWT 2.4的内置验证,而不是GWT-Validation)。
同样,为了获得一些重用,我制作了一对类,ValidatableInputCell 和 AbstractValidatableColumn。我从以下地方获得了灵感:
- http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/validation/src/main/java/com/google/gwt/sample/validation/client/ValidationView .java?r=10642
- http://gwt.google.com/samples/Showcase/Showcase.html#!CwCellValidation
让我们看一下......
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 <input> 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;
}
哦!这是带有 JSR-303 注释的 DTO...
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 中删除断点我希望在每次击键和/或单元格失去焦点后触发验证。它永远不会被调用。我可以在牢房中输入任何我喜欢的内容。有关修复方法的任何线索吗?
我的早期想法... a) AbstractValidatableColumn#getFieldUpdater 永远不会被调用,b) ValidatableInputCell#onBrowserEvent 或 ValidatableInputCell#render 中的逻辑需要彻底修改。
最终,我希望看到每个违反约束的单元格旁边出现一个弹出窗口,当然还希望看到应用了适当的颜色。
As a follow-up to Why are there no decent examples of CompositeCell in use within a CellTable?
I am trying to add-on JSR-303 validation support. I have followed Koma's config advice here: How to install gwt-validation with gwt-2.4.0 (Note: I'm using GWT 2.4's built-in validation, not GWT-Validation).
Again, in order to get some re-use I crafted a pair of classes, ValidatableInputCell and AbstractValidatableColumn. I got inspiration for them from:
- http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/validation/src/main/java/com/google/gwt/sample/validation/client/ValidationView.java?r=10642
- http://gwt.google.com/samples/Showcase/Showcase.html#!CwCellValidation
Let's have a look at 'em...
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 <input> 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;
}
}
}
and
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;
}
}
I use the AbstractValidatableColumn like so...
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;
}
Oh! And here's the DTO with JSR-303 annotations...
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;
...
}
Dropping a breakpoint in onBrowserEvent I would expect to have the validation trigger on each key stroke and/or after cell loses focus. It never gets invoked. I can enter whatever I like in the cell. Any clues as to an approach to fix?
My early thoughts... a) AbstractValidatableColumn#getFieldUpdater is never getting invoked and b) the logic in either ValidatableInputCell#onBrowserEvent or ValidatableInputCell#render needs an overhaul.
Ultimately, I'd like to see a popup appearing next to each cell that violates a constraint, and of course see that the appropriate coloring is applied.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
来这里呼吸空气。 我终于找到了解决方案!我选择使用GWT Validation库,请参阅http://code.google.com/p/gwt-validation/wiki/GWT_Validation_2_0(已知以下代码可与2.1 快照)。
对单元格执行验证时的技巧是调用 validateValue 而不是 validate (后者会触发对所有实体字段的验证)。此外,所有输入单元格值都是字符串,并在验证之前转换为相应的实体字段类型。 (甚至适用于嵌套实体字段)。
以下是 AbstractValidatableColumn (AVC) 和 ValidatableInputCell 的修订版实现。
AVC 的变体可能看起来像...
ConversionResult 由列的 fieldUpdater 查阅。它看起来像这样...
最后,这是您在网格中指定列的方式。
请注意,上面示例中的 DTO 的字段带有 JSR-303 约束注释(例如,使用 @Digits、@NotNull)。
以上需要做一些工作,它可能是目前网络上最全面的解决方案。享受!
Coming up for air here. I finally figured out a solution! I opted to employ the GWT Validation library, see http://code.google.com/p/gwt-validation/wiki/GWT_Validation_2_0 (the code below is known to work with the 2.1 SNAPSHOT).
The trick when performing validation for a cell is to call validateValue rather than validate (the latter triggers validation for all entity's fields). As well, all input cell values are String, and converted to the respective entity field type before being validated. (Even works for nested entity fields).
Here's the revised impls for both AbstractValidatableColumn (AVC) and ValidatableInputCell.
Variants of AVC might look like...
A ConversionResult is consulted by a Column's fieldUpdater. Here's what it looks like...
Finally, here's how you might spec a column in a grid
Note the DTO's in the example above have their fields JSR-303 constraint annotated (e.g., with @Digits, @NotNull).
The above took some doing, and it may just be the most comprehensive solution out on the net at the moment. Enjoy!
我不清楚为什么从
generatePriceColumn
返回HasCell
,因为除了CompositeCell
之外,几乎任何东西都不能使用它。 - 也许您正在尝试将所有这些都封装在一个更大的单元中。在询问之前,您可能会考虑将来进一步分解您的示例,问题可能会变得清晰。我更改了“列”创建代码,因此它实际上返回了一个列 - 这意味着更改 AbstractValidatableColumn 以扩展列。一路上,我注意到您重写了 getFieldUpdater,而不修改底层字段,这将阻止 Column 内部的其他部分在查找该字段时工作。因此,我最初的实验是正确地处理
ValidatableInputCell.onBrowserEvent
的 keyup 大小写,但是没有ValueUpdater
实例可以使用,因为列中的 FieldUpdater
为空。此时,我没有连接的验证逻辑正在被调用 - 从 GWT 2.4.0 开始,这在每个类中仍然被标记为“实验性”,并且不用于生产代码,所以我直到 2.5.0 左右,粗糙的边缘已经被磨圆了。如果我要继续(并且如果您有问题),我将从 http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/validation/ - 使其正常工作,然后窃取详细信息,直到我的工作正常 出色地。
其他一些观察结果:
不要扩展类来添加功能,除非您希望/允许该类的任何使用者像使用子类一样使用它。在这种情况下很难说,但是
generatePriceColumn
似乎位于CellTable
子类上,它CellTable
方法一样 - 其他以列为中心的方法实际上添加列而不是返回它CellTable
(因为这就是您子类),而该方法将否则与AbstractCellTable
子类(如DataTable
)(较新的CellTable
)配合得很好。在这种情况下,我要么将方法更改为
addPriceColumn(...)
,并让它使用 Column 并将其添加到列表中,或者将其保留在列表中,或者作为子类列,或者完全独立作为实用程序方法。我最终的 AbstractValidationColumn 最终没有太多理由成为子类,实际上只是 Column 的一个方便构造函数:FieldUpdater 是这里有趣的部分,这就是应该关注的部分,并留下尽可能多的其他部分以供重用可能的。这将允许任何单元在准备好时运行自己的 ValueUpdater - 也许不像您希望的那么频繁,但它通常会使事情更容易更快地使用。创建一个封装另一个 FieldUpdater 的 FieldUpdater impl,它可以特定于在这种情况下正在更改的任何字段。
我认为这里潜伏着另一个错误,如果您单独测试列/字段更新程序,则可能会出现 - 在运行验证之前,新值不会应用于 T 类型的 bean,因此正在使用旧的有效值。需要尽快调用
doUpdate
。最后,我鼓励你让你的例子保持简单——一些脑死亡的“是否为空”检查验证,一个简单直接的 CellTable 设置会让你看到列本身只有验证内容在工作如果 Column.fieldUpdater 字段不为空。从更简单、有效的配置开始构建,因此每个阶段只会出现一件事。
It isn't clear to me why a
HasCell
is being returned fromgeneratePriceColumn
, since that can't be consumed by practically anything, exceptCompositeCell
- maybe you are trying to wrap up all of this in a bigger cell. Before asking, you might consider in the future breaking down your example further, the problem might become clear.I changed the 'column' creating code so it actually returned a Column - this meant changing AbstractValidatableColumn to extend Column. Along the way, I noticed that you were overriding getFieldUpdater, without modifying the underlying field, which will prevent other pieces of Column's internals from working, as they look for that field. As a result of this, my initial experiments were getting to
ValidatableInputCell.onBrowserEvent
's keyup case correctly, but there was noValueUpdater
instance to work with, since theFieldUpdater
was null in Column.At that point, the validation logic, which I didn't wire up, is being invoked - As of GWT 2.4.0, this is still tagged in every class as "EXPERIMENTAL", and as not for use in production code, so I've given it a pass until 2.5.0 or so, when the rough edges have been rounded off. If I were to continue though (and if you have issues), I'd start with the project at http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/validation/ - get that to work, and then steal details until mine worked as well.
A few other observations:
Don't extend classes to add functionality, except where you expect/allow any consumers of that class to use it as they would the subclass. Hard to tell in this case, but
generatePriceColumn
appears to be on aCellTable
subclass, whichCellTable
method - other column-focused methods actually add the column instead of returning itCellTable
(since that is what you subclass) while that method will work perfectly well otherwise withAbstractCellTable
subclasses likeDataTable
, a newerCellTable
In this case, I'd either change the method to be
addPriceColumn(...)
, and have it use a Column and add it to the list, or keep it out, either as a subclassed column, or entirely on its own as a utility method. My final AbstractValidationColumn ended up not having much reason to be a subclass at all, effectively just a convenience constructor for Column:The FieldUpdater is the interesting part here, that is what should be focused on, and leave as many other pieces to be reused as possible. This will allow any cell to run its own ValueUpdater when it is ready - perhaps not as frequently as you like, but it'll generally make things easier to use more quickly. Make a FieldUpdater impl that wraps another FieldUpdater, which can be specific to whatever field is being changed in that case.
I think another bug is lurking here, and might show up if you test the column/fieldupdater on its own - the new value isn't applied to the bean of type T until after the validation has run, so the bean is being validated with the old valid value.
doUpdate
needs to be called sooner.And finally, I'd encourage you to keep your example simpler as you go - some brain-dead 'is it null' check for validation, and a simple straightforward CellTable setup would let you see that the column itself only has the validation stuff working if the
Column.fieldUpdater
field is non null. Build up from a simpler configuration that works, so only one thing can go wrong at each stage.