cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sylv...@apache.org
Subject cvs commit: cocoon-2.1/src/blocks/forms/samples/forms carselector_form.xml form1.xml
Date Tue, 29 Jun 2004 13:06:04 GMT
sylvain     2004/06/29 06:06:04

  Modified:    src/blocks/forms/java/org/apache/cocoon/forms/formmodel
                        AbstractDatatypeWidgetDefinition.java
                        AggregateField.java Field.java Union.java
               src/blocks/forms/samples/forms carselector_form.xml
                        form1.xml
  Log:
  Refactored Field's state automata:
  - getValue() no more has the side effect of displaying validation errors, even if validation
still occurs.
  - validators are always called on validate() since validation may depend on other fields
whose value may have changed since last validation.
  
  Updated Union so that, when the case value changes, values entered for the previous case
value are not lost.
  
  Revision  Changes    Path
  1.3       +5 -1      cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractDatatypeWidgetDefinition.java
  
  Index: AbstractDatatypeWidgetDefinition.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractDatatypeWidgetDefinition.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- AbstractDatatypeWidgetDefinition.java	9 Mar 2004 11:31:12 -0000	1.2
  +++ AbstractDatatypeWidgetDefinition.java	29 Jun 2004 13:06:04 -0000	1.3
  @@ -98,5 +98,9 @@
       public boolean hasValueChangedListeners() {
           return this.listener != null;
       }
  +    
  +    public ValueChangedListener getValueChangedListener() {
  +        return this.listener;
  +    }
   
   }
  
  
  
  1.11      +6 -4      cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AggregateField.java
  
  Index: AggregateField.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AggregateField.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- AggregateField.java	7 May 2004 16:43:43 -0000	1.10
  +++ AggregateField.java	29 Jun 2004 13:06:04 -0000	1.11
  @@ -98,7 +98,7 @@
           if (newEnteredValue != null) {
               // There is one aggregated entered value. Read it and split it.
               super.readFromRequest(formContext);
  -            if (needsParse) {
  +            if (this.valueState == VALUE_UNPARSED) {
                   setFieldsValues(enteredValue);
               }
           } else {
  @@ -107,7 +107,9 @@
               for (Iterator i = fields.iterator(); i.hasNext();) {
                   Field field = (Field)i.next();
                   field.readFromRequest(formContext);
  -                needsParse |= field.needsParse;
  +                if (field.valueState == VALUE_UNPARSED) {
  +                    this.valueState = VALUE_UNPARSED;
  +                }
               }
               if (needsParse) {
                   combineFields();
  @@ -117,7 +119,7 @@
   
       public void setValue(Object newValue) {
           super.setValue(newValue);
  -        if (needsValidate) {
  +        if (this.valueState == VALUE_PARSED) {
               setFieldsValues(enteredValue);
           }
       }
  
  
  
  1.18      +167 -62   cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java
  
  Index: Field.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java,v
  retrieving revision 1.17
  retrieving revision 1.18
  diff -u -r1.17 -r1.18
  --- Field.java	1 Jun 2004 16:40:48 -0000	1.17
  +++ Field.java	29 Jun 2004 13:06:04 -0000	1.18
  @@ -55,12 +55,59 @@
   
       protected String enteredValue;
       protected Object value;
  +    
  +    /**
  +     * Value state indicating that a new value has been read from the request,
  +     * but has not yet been parsed.
  +     */
  +    protected final static int VALUE_UNPARSED = 0;
  +    
  +    /**
  +     * Value state indicating that a value has been parsed, but needs to be
  +     * validated (that must occur before the value is given to the application)
  +     */
  +    protected final static int VALUE_PARSED = 1;
  +    
  +    /**
  +     * Value state indicating that a parse error was encountered but should not
  +     * yet be displayed.
  +     */
  +    protected final static int VALUE_PARSE_ERROR = 2;
  +    
  +    /**
  +     * Value state indicating that validate() has been called when state was
  +     * VALUE_PARSE_ERROR. This makes the error visible on output.
  +     */
  +    protected final static int VALUE_DISPLAY_PARSE_ERROR = 3;
  +    
  +    /**
  +     * Transient value state indicating that validation is going on.
  +     * 
  +     * @see #validate()
  +     */
  +    protected final static int VALUE_VALIDATING = 4;
  +    
  +    /**
  +     * Value state indicating that validation has occured, but that any error should not
  +     * yet be displayed.
  +     */
  +    protected final static int VALUE_VALIDATED = 5;
  +    
  +    /**
  +     * Value state indicating that value validation has occured, and the
  +     * validation error, if any, should be displayed.
  +     */
  +    protected final static int VALUE_DISPLAY_VALIDATION = 6;
  +    
  +    // At startup, we have no value to parse (both enteredValue and value are null),
  +    // but need to validate (e.g. error if field is required)
  +    protected int valueState = VALUE_PARSED;
   
  -    // At startup, we don't need to parse (both enteredValue and value are null),
  -    // but need to validate (error if field is required)
  -    protected boolean needsParse = true;
  -    protected boolean needsValidate = true;
  -    private boolean isValidating;
  +    
  +    /**
  +     * Transient widget processing state indicating that the widget is currently validating
  +     * (used to avoid endless loops when a validator calls getValue)
  +     */
       protected ValidationError validationError;
   
   
  @@ -77,53 +124,22 @@
       }
   
       public Object getValue() {
  +        // if getValue() is called on this field while we're validating, then it's because
a validation
  +        // rule called getValue(), so then we just return the parsed (but not VALUE_VALIDATED)
value to avoid an endless loop
  +        if (this.valueState == VALUE_VALIDATING) {
  +            return this.value;
  +        }
  +        
           // Parse the value
  -        if (this.needsParse) {
  -            // Clear value, it will be recomputed
  -            this.value = null;
  -            if (this.enteredValue != null) {
  -                // Parse the value
  -                ConversionResult conversionResult = getDatatype().convertFromString(this.enteredValue,
getForm().getLocale());
  -                if (conversionResult.isSuccessful()) {
  -                    this.value = conversionResult.getResult();
  -                    this.needsParse = false;
  -                    this.needsValidate = true;
  -                } else {        // Conversion failed
  -                    this.validationError = conversionResult.getValidationError();
  -                    // No need for further validation (and need to keep the above error)
  -                    this.needsValidate = false;
  -                }
  -            } else {
  -                this.needsParse = false;
  -                this.needsValidate = true;
  -            }
  +        if (this.valueState == VALUE_UNPARSED) {
  +            doParse();
           }
   
  -        // if getValue() is called on this field while we're validating, then it's because
a validation
  -        // rule called getValue(), so then we just return the parsed (but not validated)
value to avoid an endless loop
  -        if (isValidating) {
  -            return value;
  +        // Validate the value if it was successfully parsed
  +        if (this.valueState == VALUE_PARSED) {
  +            doValidate();
           }
   
  -        // Validate the value
  -        if (this.needsValidate) {
  -            isValidating = true;
  -            try {
  -                if (super.validate()) {
  -                    // New-style validators were successful. Check the old-style ones.
  -                    if (this.value != null) {
  -                        this.validationError = getDatatype().validate(value, new ExpressionContextImpl(this));
  -                    } else {        // No value : is it required ?
  -                        if (getFieldDefinition().isRequired()) {
  -                            this.validationError = new ValidationError(new I18nMessage("general.field-required",
Constants.I18N_CATALOGUE));
  -                        }
  -                    }
  -                }
  -                this.needsValidate = false;
  -            } finally {
  -                isValidating = false;
  -            }
  -        }
           return this.validationError == null ? this.value : null;
       }
   
  @@ -139,16 +155,15 @@
           // (null allows to reset validation error)
           if (changed || newValue == null) {
               this.value = newValue;
  -            this.needsParse = false;
               this.validationError = null;
               // Force validation, even if set by the application
  -            this.needsValidate = true;
  +            this.valueState = VALUE_PARSED;
               if (newValue != null) {
                   this.enteredValue = getDatatype().convertToString(newValue, getForm().getLocale());
               } else {
                   this.enteredValue = null;
               }
  -            if (changed) {
  +            if (changed && hasValueChangedListeners()) {
                   getForm().addWidgetEvent(new ValueChangedEvent(this, oldValue, newValue));
               }
           }
  @@ -156,7 +171,12 @@
   
       public void readFromRequest(FormContext formContext) {
           String newEnteredValue = formContext.getRequest().getParameter(getRequestParameterName());
  -        readFromRequest(newEnteredValue);
  +        // FIXME: Should we consider only non-null values, which allows to
  +        // split a form across several screens?
  +        //if (newEnteredValue != null) {
  +            readFromRequest(newEnteredValue);
  +        //}
  +        
       }
   
       protected void readFromRequest(String newEnteredValue) {
  @@ -172,30 +192,110 @@
           // Only convert if the text value actually changed. Otherwise, keep the old value
           // and/or the old validation error (allows to keep errors when clicking on actions)
           if (!(newEnteredValue == null ? "" : newEnteredValue).equals((enteredValue == null
? "" : enteredValue))) {
  -            // TODO: Hmmm...
  -            getForm().addWidgetEvent(new DeferredValueChangedEvent(this, value));
               enteredValue = newEnteredValue;
               validationError = null;
               value = null;
  -            needsParse = true;
  +            this.valueState = VALUE_UNPARSED;
  +            
  +            if (hasValueChangedListeners()) {
  +	        	    // Throw an event that will parse the value only if needed.
  +	    	        getForm().addWidgetEvent(new DeferredValueChangedEvent(this, value));
  +    	        }
           }
  -
  -        // Always revalidate, as validation may depend on the value of other fields
  -        this.needsValidate = true;
       }
   
       public boolean validate() {
  -        // If needed, getValue() will do the validation
  -        getValue();
  +        if (this.valueState == VALUE_UNPARSED) {
  +            doParse();
  +        }
  +        
  +        // Force validation on already validated values (but keep invalid parsings)
  +        if (this.valueState >= VALUE_VALIDATED) {
  +            this.valueState = VALUE_PARSED;
  +        }
  +        
  +        if (this.valueState == VALUE_PARSED) {
  +            doValidate();
  +            this.valueState = VALUE_DISPLAY_VALIDATION;
  +        } else if (this.valueState == VALUE_PARSE_ERROR) {
  +            this.valueState = VALUE_DISPLAY_PARSE_ERROR;
  +        }
  +        
           return this.validationError == null;
       }
  +    
  +    /**
  +     * Parse the value that has been read from the request.
  +     * Should be called when valueState is VALUE_UNPARSED.
  +     * On exit, valueState is set to either:
  +     * - VALUE_PARSED: successful parsing or null value. Value is set and ValidationError
  +     *   is cleared.
  +     * - VALUE_PARSE_ERROR: datatype parsing error. In that case, value is null and
  +     *   validationError is set.
  +     */
  +    private void doParse() {
  +        if (this.valueState != VALUE_UNPARSED) {
  +            throw new IllegalStateException("Field is not in UNPARSED state (" + this.valueState
+ ")");
  +        }
  +        // Clear value, it will be recomputed
  +        this.value = null;
  +        this.validationError = null;
  +        if (this.enteredValue != null) {
  +            // Parse the value
  +            ConversionResult conversionResult = getDatatype().convertFromString(this.enteredValue,
getForm().getLocale());
  +            if (conversionResult.isSuccessful()) {
  +                this.value = conversionResult.getResult();
  +                this.valueState = VALUE_PARSED;
  +            } else {
  +                   // Conversion failed
  +                this.validationError = conversionResult.getValidationError();
  +                // No need for further validation (and need to keep the above error)
  +                this.valueState = VALUE_PARSE_ERROR;
  +            }
  +        } else {
  +               // No value: needs to be validated
  +               this.valueState = VALUE_PARSED;
  +        }
  +    }
  +    
  +    /**
  +     * Validate the value once it has been parsed.
  +     * Should be called when valueState is VALUE_PARSED.
  +     * On exit, valueState is set to VALUE_VALIDATED, and validationError is set if
  +     * validation failed.
  +     */
  +    private void doValidate() {
  +        if (this.valueState != VALUE_PARSED) {
  +            throw new IllegalStateException("Field is not in PARSED state (" + this.valueState
+ ")");
  +        }
  +        
  +        // Go to transient validating state
  +        this.valueState = VALUE_VALIDATING;
  +        
  +        try {
  +            if (this.value == null && getFieldDefinition().isRequired()) {
  +                // Field is required
  +                this.validationError = new ValidationError(new I18nMessage("general.field-required",
Constants.I18N_CATALOGUE));
  +            } else {
  +                if (super.validate()) {
  +                    // New-style validators were successful. Check the old-style ones.
  +                    this.validationError = getDatatype().validate(value, new ExpressionContextImpl(this));
               
  +                }
  +            }
  +        } finally {
  +            // Consider validation finished even in case of exception
  +            this.valueState = VALUE_VALIDATED;
  +        }
  +    }
   
       /**
        * Returns the validation error, if any. There will always be a validation error in
case the
        * {@link #validate()} method returned false.
        */
       public ValidationError getValidationError() {
  -        return validationError;
  +        // If needed, getValue() will do the validation
  +        getValue();
  +        return this.validationError;
       }
   
       /**
  @@ -206,6 +306,7 @@
        */
       public void setValidationError(ValidationError error) {
           this.validationError = error;
  +        this.valueState = VALUE_DISPLAY_VALIDATION;
       }
   
       public boolean isRequired() {
  @@ -248,7 +349,7 @@
           }
   
           // validation message element: only present if the value is not valid
  -        if (validationError != null) {
  +        if (validationError != null && (this.valueState == VALUE_DISPLAY_VALIDATION
|| this.valueState == VALUE_DISPLAY_PARSE_ERROR)) {
               contentHandler.startElement(Constants.INSTANCE_NS, VALIDATION_MSG_EL, Constants.INSTANCE_PREFIX_COLON
+ VALIDATION_MSG_EL, XMLUtils.EMPTY_ATTRIBUTES);
               validationError.generateSaxFragment(contentHandler);
               contentHandler.endElement(Constants.INSTANCE_NS, VALIDATION_MSG_EL, Constants.INSTANCE_PREFIX_COLON
+ VALIDATION_MSG_EL);
  @@ -327,6 +428,10 @@
   
       public void removeValueChangedListener(ValueChangedListener listener) {
           this.listener = WidgetEventMulticaster.remove(this.listener, listener);
  +    }
  +    
  +    private boolean hasValueChangedListeners() {
  +        return this.listener != null || this.fieldDefinition.hasValueChangedListeners();
       }
   
       private void fireValueChangedEvent(ValueChangedEvent event) {
  
  
  
  1.12      +19 -7     cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Union.java
  
  Index: Union.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Union.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- Union.java	7 May 2004 16:43:43 -0000	1.11
  +++ Union.java	29 Jun 2004 13:06:04 -0000	1.12
  @@ -32,6 +32,7 @@
       //      XSLT post-processing, the choice of element-name reflects this.
       private static final String UNION_EL = "field";
       private Widget caseWidget;
  +    private String caseValue;
       
       private final UnionDefinition definition;
   
  @@ -78,13 +79,24 @@
           
           Widget widget;
           // Read current case from request
  -        String value = (String)getValue();
  -        if (value != null && !value.equals(""))
  -            if ((widget = getChild(value)) != null)
  +        String newValue = (String)getValue();
  +        if (newValue != null && !newValue.equals("")) {
  +            
  +            if (getForm().getSubmitWidget() == caseWidget && !newValue.equals(caseValue))
{
  +                // If submitted by the case widget and its value has changed, read the
values
  +                // for the previous case values. This allows to keep any entered values
  +                // despite the case change.
  +                widget = getChild(caseValue);
  +            } else {
  +                // Get the corresponding widget (will create it if needed)
  +                widget = getChild(newValue);
  +            }
  +            
  +            if (widget != null) {
                   widget.readFromRequest(formContext);
  -
  -        // Read union discriminant value from request
  -        //item.readFromRequest(formContext);
  +            }
  +        }
  +        caseValue = newValue;
       }
   
       // TODO: Simplify this logic.
  
  
  
  1.4       +0 -4      cocoon-2.1/src/blocks/forms/samples/forms/carselector_form.xml
  
  Index: carselector_form.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/samples/forms/carselector_form.xml,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- carselector_form.xml	7 May 2004 20:54:21 -0000	1.3
  +++ carselector_form.xml	29 Jun 2004 13:06:04 -0000	1.4
  @@ -33,8 +33,6 @@
               // Get the corresponding type list
               typewidget.setSelectionList("cocoon:/cars/" + value);
             } else {
  -            // Reset the value (will clear validation error)
  -            event.source.setValue(null);
               // Set an empty selection list
               typewidget.setSelectionList(new Packages.org.apache.cocoon.forms.datatype.EmptySelectionList("Select
a maker first"));
             }
  @@ -74,8 +72,6 @@
             if (value != null) {
               modelwidget.setSelectionList("cocoon:/cars/" + makewidget.value + "/" + value);
             } else {
  -            // Reset the value (will clear validation error)
  -            event.source.setValue(null);
               // Set an empty selection list
               modelwidget.setSelectionList(new Packages.org.apache.cocoon.forms.datatype.EmptySelectionList("Select
a type first"));
             }
  
  
  
  1.8       +7 -1      cocoon-2.1/src/blocks/forms/samples/forms/form1.xml
  
  Index: form1.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/samples/forms/form1.xml,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- form1.xml	7 May 2004 20:54:21 -0000	1.7
  +++ form1.xml	29 Jun 2004 13:06:04 -0000	1.8
  @@ -281,6 +281,12 @@
           <fd:field id="firstname">
             <fd:label>Firstname</fd:label>
             <fd:datatype base="string"/>
  +          <!-- very simple example of creation events (should really find something
better) -->
  +          <fd:on-create>
  +            <fd:javascript>
  +              java.lang.System.err.println("Creating a new contact row");
  +            </fd:javascript>
  +          </fd:on-create>
           </fd:field>
           <fd:field id="lastname">
             <fd:label>Lastname</fd:label>
  
  
  

Mime
View raw message