So here is my solution (based on David's great feedback):
First, there is a limitation in the TextInputCell in that it can't handle native keypress
events, so I had to create a near-exact copy of the class and add the keypress
event type to the constructor where it passes in the event types that the cell can handle to the super constructor. Let's call this class KeyPressableTextInputCell
. See this thread where I discussed this solution: Why isn't the keypress event being sent to GWT's TextInputCell.onBrowserEvent() method?
Then, I created a MaskedTextInputCell class that overrides the onBrowserEvent()
method and prevents invalid keystrokes as they're typing, and also checks the overall form of the field. For example, valid monetary keystrokes are 0-9 and the decimal point. The overall form has to be numbers only, with only a single decimal point. Both of these checks are done using a strategy interface that I called ValidationStrategy
and put directly in the MaskedTextInputCell.
public class MaskedTextInputCell extends KeyPressableTextInputCell {
public interface ValidationStrategy {
public boolean matches(String valueToCheck);
}
private ValidationStrategy overallFormValidationStrategy;
private ValidationStrategy validKeystrokeValidationStrategy;
public MaskedTextInputCell(ValidationStrategy overallFormValidationStrategy,
ValidationStrategy validKeystrokeValidationStrategy) {
this.overallFormValidationStrategy = overallFormValidationStrategy;
this.validKeystrokeValidationStrategy = validKeystrokeValidationStrategy;
}
@Override
public void onBrowserEvent(Element parent, String value, Object key, NativeEvent event,
ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(parent, value, key, event, valueUpdater);
if ("keypress".equals(event.getType())) {
String keystroke = String.valueOf((char) event.getCharCode());
handleInvalidKeystroke(keystroke, event);
} else if ("blur".equals(event.getType()) || "keyup".equals(event.getType())) {
String valueInInputElement = getInputElement(parent).getValue();
handleInvalidOverallForm(valueInInputElement);
}
}
protected void handleInvalidOverallForm(String valueOfEntireField) {
if (!overallFormValidationStrategy.matches(valueOfEntireField)) {
//You could fire an event here to turn the cell red...
GWT.log("Invalid form.");
}
}
protected void handleInvalidKeystroke(String keystroke, NativeEvent event) {
if (!validKeystrokeValidationStrategy.matches(keystroke)) {
GWT.log("Invalid keystroke.");
event.preventDefault();
}
}
}
I then created a regular expression implementation of the ValidationStrategy:
public class RegularExpressionValidationStrategy implements MaskedTextInputCell.ValidationStrategy {
private String regularExpression;
public RegularExpressionValidationStrategy(String regularExpression) {
this.regularExpression = regularExpression;
}
@Override
public boolean matches(String valueToCheck) {
return valueToCheck.matches(regularExpression);
}
}
Now, I can do something like this to create a money field:
public class MonetaryTextInputCell extends MaskedTextInputCell {
public MonetaryTextInputCell() {
super(new RegularExpressionValidationStrategy("[0-9.]"),
new RegularExpressionValidationStrategy("^[0-9.][0-9]*[0-9.]?[0-9]*$"));
}
}