2012-02-03 9 views
11

Jako kontynuacja doPoszukując przykładu sprawdzania GWT ... gdzie jesteś?

Próbuję dodać obsługę sprawdzania poprawności JSR-303. Śledziłem konfigurację Komy tutaj: How to install gwt-validation with gwt-2.4.0 (Uwaga: Używam wbudowanej weryfikacji GWT 2.4, a nie GWT-Validation).

Ponownie, aby uzyskać pewne ponowne wykorzystanie I spreparowane parę klas, ValidatableInputCell i AbstractValidatableColumn. Mam dla nich inspirację z:

Rzućmy okiem na „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 &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; 
    } 
} 

} 

i

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 nas E AbstractValidatableColumn jak tak ...

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! A oto Dto z JSR-303 adnotacji ...

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; 

... 

} 

Usunięcie przerwania w onBrowserEvent spodziewałbym mieć spust walidacji na każdym naciśnięciu klawisza i/lub po komórka traci ostrość. Nigdy nie zostanie wywołany. Mogę wprowadzić cokolwiek mi się podoba w celi. Jakieś wskazówki dotyczące podejścia do naprawy?

Moje wczesne przemyślenia ... a) AbstractValidatableColumn # getFieldUpdater nigdy nie jest wywoływana i b) logika w ValidatableInputCell # onBrowserEvent lub ValidatableInputCell # render wymaga przebudowy.

Ostatecznie chciałbym zobaczyć wyskakujące okienko obok każdej komórki, które narusza ograniczenie, i oczywiście zobaczyć, że zastosowano odpowiednią kolorystykę.

+0

Przeglądając teraz - możesz chcieć usunąć 'UiResources.INSTANCE.style(). Error()', ponieważ nie skompiluje się bez twojej własnej klasy UiResources. Nadklasa 'DateComparable' prawdopodobnie też powinna pójść, a' generatePriceColumn' mogłaby prawdopodobnie użyć dodatkowego uproszczenia dla klas 'isInEditMode' oraz' DisplayMode'. –

+0

UiResources to mój własny ClientBundle. DateComparable już dawno minęło. Wszystkie daty są ciągiem ISO. Konwertujemy po stronie serwera, abyśmy mogli przekonwertować odpowiednią strefę czasową. –

+1

Czy to tylko ja, czy też walidacja w GWT jest głupio złożonym problemem, kiedy powinno być naprawdę proste? Jesteśmy teraz na etapie, gdzie Google pozwalający społeczność open-source, aby przejąć kontrolę nad GWT i nadal nie da się łatwo sprawdzić poprawność danych wejściowych na polu ... albo ja czegoś brakuje? – slugmandrew

Odpowiedz

3

Nadchodzi tu powietrze. W końcu wymyśliłem rozwiązanie! Zdecydowałem się użyć biblioteki GWT Validation, zobacz http://code.google.com/p/gwt-validation/wiki/GWT_Validation_2_0 (poniższy kod działa w połączeniu z 2.1 SNAPSHOT).

Sztuką przy przeprowadzaniu walidacji komórce jest wywołanie validateValue zamiast poprawności (walidacji ostatnie wyzwalaczy dla wszystkich pól jednostki). Jak również, wszystkie wartości komórek wejściowych są łańcuchami i konwertowane na odpowiedni typ pola jednostki przed sprawdzeniem poprawności. (Działa nawet dla zagnieżdżonych pól encji).

Oto zmienione impls zarówno dla AbstractValidatableColumn (AVC) oraz 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; 
    } 
} 

} 

Warianty AVC może wyglądać ...

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

ConversionResult jest konsultowany przez kolumnę za fieldUpdater. Oto jak to wygląda ...

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

Wreszcie, oto jak można wg planu kolumny w siatce

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

     }; 

Zanotować DTO jest w przykładzie powyżej, ich pola JSR-303 ograniczeniem uwagami (np. z @Digits, @NotNull).

Powyższe zrobiło trochę, a może to być najbardziej kompleksowe rozwiązanie obecnie w sieci. Cieszyć się!

1

To nie jest dla mnie jasne, dlaczego HasCell jest zwracany z generatePriceColumn, ponieważ nie mogą być spożywane przez praktycznie niczego, z wyjątkiem CompositeCell - może próbujesz zawinąć wszystko to w większej celi. Zanim zapytasz, możesz zastanowić się nad dalszym rozbijaniem twojego przykładu, problem może stać się jasny.

Zmieniłem kod tworzący kolumnę, więc faktycznie zwrócił kolumnę - oznaczało to zmianę AbstractValidatableColumn w celu rozszerzenia kolumny. Po drodze zauważyłem, że nadpisujesz getFieldUpdater, bez modyfikowania podstawowego pola, które uniemożliwi działanie innych elementów Column, gdy będą szukać tego pola. W wyniku tego moje początkowe eksperymenty trafiały poprawnie do sprawy kluczowania ValidatableInputCell.onBrowserEvent, ale nie było żadnej instancji ValueUpdater, z którą można by pracować, ponieważ FieldUpdater miał wartość NULL w kolumnie.

W tym momencie wywoływana jest logika sprawdzania poprawności, która nie została powiązana - od GWT 2.4.0 jest ona nadal oznaczana w każdej klasie jako "EKSPERYMENTALNA" i nie jest używana w produkcji kod, więc dałem jej przepustkę do 2.5.0, gdy szorstkie krawędzie zostały zaokrąglone.Gdybym miał kontynuować (i jeśli masz problemy), zacznę od projektu pod numerem http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/validation/ - zacznij działać, a następnie kradnij szczegóły, dopóki moje też nie będą działać.

Kilka inne obserwacje:

nie rozciągają klas, aby dodać funkcjonalność, z wyjątkiem przypadków można oczekiwać/zezwalają na dowolne konsumenci tej klasy używać go tak samo, jak podklasy. Trudno powiedzieć, w tym przypadku, ale generatePriceColumn wydaje się być na CellTable podklasy, które

  1. Umożliwia dowolny kod, który używa go zmienić jak reszta CellTable skonfigurowaniu
  2. naprawdę nie zachowywać się jak CellTable metoda - inne metody kolumn koncentruje faktycznie dodać kolumnę zamiast wrócić to
  3. Może zablokować Ci, aby zawsze używać CellTable (ponieważ to właśnie podklasy), podczas gdy ta metoda działa doskonale, inaczej z AbstractCellTable podklasy jak DataTable, nowszy CellTable

W tym przypadku chciałbym zmienić metodę na addPriceColumn(...), a następnie użyć kolumny i dodać ją do listy lub zachować ją jako kolumnę podklasowaną lub całkowicie jako własną. metoda użyteczności. Moja ostatnia AbstractValidationColumn skończyło się nie mając wiele powodów do podklasy w ogóle, skutecznie tylko konstruktor wygoda dla kolumny:

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 jest interesująca część tutaj, że to, co powinno być ukierunkowane na, i pozostawić jako wiele innych elementów do ponownego wykorzystania, jak to możliwe. Umożliwi to każdej komórce uruchomienie własnej ValueUpdater, gdy będzie ona gotowa - być może nie tak często, jak chcesz, ale ogólnie ułatwi korzystanie z niej szybciej. Utwórz implikację FieldUpdater, która owija inny FieldUpdater, który może być specyficzny dla dowolnego pola, które jest zmieniane w tym przypadku.

Myślę, że kolejny błąd czai się tutaj i może pojawić się, jeśli samodzielnie przetestujesz kolumnę/fieldupdater - nowa wartość nie zostanie zastosowana do komponentu typu T, dopóki nie zostanie uruchomione sprawdzanie poprawności, więc komponent bean jest sprawdzany ze starą poprawną wartością. doUpdate musi być nazywane wcześniej.

I na koniec zachęcam cię do tego, abyś przykładał prostotę, kiedy jesteś w ruchu - jakaś martwa dla mózgu kontrola "jest zerowa" dla sprawdzania poprawności, a prosta prosta konfiguracja CellTable pozwoliłaby ci zobaczyć, że sama kolumna ma tylko sprawdzanie poprawności działa, jeśli pole Column.fieldUpdater nie ma wartości null. Buduj z prostszej konfiguracji, która działa, więc tylko jedna rzecz może pójść źle na każdym etapie.

+0

Colin, dzięki! Dałeś mi trochę do żucia. Muszę to przetrawić i wrócić. Aby odpowiedzieć na niektóre z punktów ... –

+0

ToggleableGrid jest podklasą I zaprojektowany, aby pomóc z dwoma wyświetlaczami tryb (nasi użytkownicy chcą ruszty mają być świadczone albo tylko do odczytu lub tylko wejście). Od tego czasu zamieniłem CellTable na DataGrid. Przypuszczam, że mógłbym zdecydować się na kompozycję ponad dziedzictwo i ukryć ukryty pod nim imp. generatePriceColumn i generateMwColumn służą do budowania kolumny złożonej. Od HasCell jest interfejsem I domniemywać była to droga o umacnianie poszczególnych kolumn, aby dodać do kolumny kompozytowego. Nazwę tę metodę, addPriceMwColumn. Ostatecznie muszę zweryfikować komórki kolumn indywidualnie. –

+0

"Buduj z prostszej konfiguracji, która działa, więc tylko jedna rzecz może pójść źle na każdym etapie." Uwierzcie mi ... to była trudna droga! IMO, rodzina widżetów Cell to jedne z najbardziej złożonych API do zrozumienia w GWT, ale także prawdopodobnie niektóre z najpotężniejszych. Próbując rzemieślniczych rozwiązań opartych na prostych przykładach dostępnych wokół netto był projekt badawczy, a ja z pewnością docenią czas Wziąłeś patrzeć na taśmy mojego kodzie (choć jeden masywny). –