Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    CustomValidator design problems and solutions

    com.smartgwt.client.widgets.form.validator.CustomValidator has 2 design problems : lack of registration facility and lack of access to the validator attributes from the data source field. The first problem is addressed in another thread (http://forums.smartclient.com/showthread.php?t=13923) and is fixed in nightly builds by adding a static method Validator.addValidatorDefinition(). The second problem though is still pending. There seem to be 2 possible paths to fixing as follows:

    One solution could be to provide published API on DataSourceField (accessible through CustomValidator.dataSourceField) for retrieval of validator definitions bound to the field. This however would be cumbersome to use in custom validators because of the need to search for the correct definition and the problems in case that there are multiple validator definitions of the same type on the field.

    Another solution could be to expose to Java code in CustomValidator one more field which points directly to the validator definition appropriate for the specific invocation of condition() and is available in the JavaScript call under the name "validator". This can be done e.g. through a Java field "protected DataClass dataSourceValidator". This field will be further managed in setup() and reset() in a fashion similar to the other already existing fields. Specifically initialization would be :
    Code:
                if(validator !=null) {
                    self.@com.smartgwt.client.widgets.form.validator.Custom Validator::dataSourceValidator = @com.smartgwt.client.core.DataClass::new(Lcom/google/gwt/core/client/JavaScriptObject;)(validator);
                }
                else {
                    self.@com.smartgwt.client.widgets.form.validator.Custom Validator:dataSourceValidator = @com.smartgwt.client.core.DataClass::new()();
                }
    and clean up in reset would obviously be "dataSourceField = null".

    It is probably clear from the above that I suggest adoption of the second solution and would be glad if Isomorphic adopts it.

    #2
    Hi nikolayo,

    Thanks for the clear analysis. We agree that the attributes specified on the validator definition (as defined directly on the dataSource field in javascript / xml) needs to be available to the live Java validator when the condition runs.

    We'll be addressing this in the near future (probably early next week).
    Thanks for the suggested approach and we'll keep you updated as soon as we have a resolution in place

    Regards
    Isomorphic Software

    Comment


      #3
      Thank you, Isomorphic! This will be very helpful.

      BTW, IMHO:
      1/ The data fields in CustomValidator should better be private rather than protected because they are under control of private methods.
      2/ The getter methods in CustomValidator should better be protected rather than public because the data they retrieve is only available during the execution of the condition() method.

      Let me also provide here some sample code for other interested readers:

      Sample custom validator:
      Code:
      import java.util.Date;
      
      /**
       * A sample custom validator. 
       */
      public class IsBeforeValidator
          extends CustomValidator
      {
          @Override
          protected boolean condition(Object value)
          {
              if (!(value instanceof Date)) {
                  // The validator is bound to
                  // a field of the wrong type.
                  return false;
              }
              String target = getDataSourceValidator().getAttribute("target");
              if (target == null) {
                  // May be typo or omission in the ds specs.
                  // Don't let it fall through to the next stage 
                  // where it would return true.
                  return false;
              }
              Date targetValue = getRecord().getAttributeAsDate(target);
              if (targetValue == null) {
                  // May be partial record where target value is not 
                  // defined yet or may be that the target is optional.
                  // So leave this to other validators to resolve.
                  return true;
              }
              // The defining condition check at last:
              if (((Date) value).before(targetValue)) {
                  return true;
              }
              else {
                  return false;
              }
          }
      }
      Registration of the custom validator in the application entry point class:
      Code:
          static {
              Validator.addValidatorDefinition("isBefore", new IsBeforeValidator());
          }
      Binding of the custom validator to a field in a data source descriptor:
      Code:
      <field 
      	name="START_DATE"
      	type="date"
      	title="Start Date">
      	<validators>
      		<validator
      			type="isBefore"
      			clientOnly="true"
      			target="END_DATE"
      			errorMessage="Start date must be before end date!"/>
      			</validators>
      </field>
      The code above can work with current production version if custom Validator and CustomValidator classes are implemented and used. Here is source for those:

      Code:
      /**
       * Custom Validator class which adds addValidatorDefinition()
       * facility available in nightly builds but missing in released 
       * version 2.3 of smartgwtee. Be careful with the 2 Validator
       * classes and import the original in all places other than
       * the application entry point class where binding of custom
       * validators should happen.
       * 
      */
      //FIXME: remove this class as smartgwtee 2.4 gets published
      public class Validator
      {
          public native static void addValidatorDefinition(String name,
                                                           com.smartgwt.client.widgets.form.validator.Validator validator) /*-{
              if (validator == null) return;
              var jsValidator = validator.@com.smartgwt.client.widgets.form.validator.Validator::getJsObj()();
              if (jsValidator.errorMessage != null) jsValidator.defaultErrorMessage = jsValidator.errorMessage;
              $wnd.isc.Validator.addValidatorDefinition(name, jsValidator);
          }-*/;
      }
      Code:
      /**
       * Custom validator class which fixes lack of access to data source
       * validator definition in the original CustomValidator. There is 
       * some cost for the fix: private method setup() is called both
       * in the base class and in this one. Let's hope this or similar
       * fix will be implemented in the original sooner or later...<p/>
       *
       * Take care to change package name "com.sample.demo.client" 
       * in setup() if/when this class goes to another package!.
       */
      //FIXME: remove this class as the problem gets fixed in future
      //releases of smartgwtee beyond 2.3
      public abstract class CustomValidator
          extends com.smartgwt.client.widgets.form.validator.CustomValidator
      {
          private DataClass dataSourceValidator;
      
          public CustomValidator()
          {
              // type is set to "custom" by parent constructor 
              setup(getJsObj());
          }
      
          /**
           * Retrieve validator attributes defined in the data source descriptor(if any)
           * 
           * @return the attributes
           */
          protected DataClass getDataSourceValidator()
          {
              return dataSourceValidator;
          }
      
          private void reset()
          {
              formItem = null;
              dataSourceField = null;
              dataSourceValidator = null;
              record = null;
          }
      
          private native void setup(JavaScriptObject jsObj) /*-{
              var self = this;
              jsObj.condition = function(item, validator, value, record) {
                  if($wnd.isc.isA.FormItem(item)) {
                     self.@com.smartgwt.client.widgets.form.validator.CustomValidator::formItem = @com.smartgwt.client.widgets.form.fields.FormItemFactory::getFormItem(Lcom/google/gwt/core/client/JavaScriptObject;)(item);
                  } else {
                      if (item.__ref && @com.smartgwt.client.data.DataSourceField::isDataSourceField(Lcom/google/gwt/core/client/JavaScriptObject;)(item)) {
                          self.@com.smartgwt.client.widgets.form.validator.CustomValidator::dataSourceField = @com.smartgwt.client.data.DataSourceField::getOrCreateRef(Lcom/google/gwt/core/client/JavaScriptObject;)(item);
                      } else {
                          var dataSourceField = $wnd.isc.addProperties({}, item);
                          if (dataSourceField.__ref) delete dataSourceField.__ref;
                          self.@com.smartgwt.client.widgets.form.validator.CustomValidator::dataSourceField = @com.smartgwt.client.data.DataSourceField::new(Lcom/google/gwt/core/client/JavaScriptObject;)(dataSourceField);
                      }
                  }
                  if(record != null) {
                      self.@com.smartgwt.client.widgets.form.validator.CustomValidator::record = @com.smartgwt.client.data.Record::getOrCreateRef(Lcom/google/gwt/core/client/JavaScriptObject;)(record);
                  }
                  if(validator !=null) {
                      self.@com.sample.demo.client.CustomValidator::dataSourceValidator = @com.smartgwt.client.core.DataClass::new(Lcom/google/gwt/core/client/JavaScriptObject;)(validator);
                  }
                  else {
                      self.@com.sample.demo.client.CustomValidator::dataSourceValidator = @com.smartgwt.client.core.DataClass::new()();
                  }
                  var valueJ = $wnd.SmartGWT.convertToJavaType(value);
                  var ret =  self.@com.smartgwt.client.widgets.form.validator.CustomValidator::condition(Ljava/lang/Object;)(valueJ);
                  self.@com.smartgwt.client.widgets.form.validator.CustomValidator::reset()();
                  return ret;
              };
          }-*/;
      }
      Regards
      Nikolay

      Comment


        #4
        The latest build has a method on CustomValidator :

        public Map getValidatorProperties() that provides access to any user defined validator properties.

        Comment


          #5
          Thank you for the prompt fix!

          Regards
          Nikolay

          Comment


            #6
            Thanks for the heads up, Nikolay... but I think I'll wait for the fix.
            I still have a lot of other stuff to do anyways ;-)

            Comment


              #7
              hi,

              how can I set the errorMessage dynamic?

              Example:
              if date1 < date2:
              message="Date1 less than Date2"

              if date1 > date2:
              message="Date1 greater than Date2"

              The method setErrorMessage(message) in the class IsBeforeValidator (example) does not work. In this case, I always get "invalid Value" message.

              Is it possible to set the error message dynamically, I need it in a listGrid with (Listgrid.setUseAllDataSourceFields)?

              Code:
              <field .....>
              	<validators>
              		<validator type="IsBeforeValidator"></validator>
              	</validators>
              </field>
              Thanks,
              Timo
              Last edited by tmoes; 20 Jan 2011, 10:12.

              Comment


                #8
                Hi tmoes,

                See this sample for how a DMI validator can make variables available to the error message (which can be a Velocity expression).

                Comment


                  #9
                  i know this example. but I need in this case, no server validator (client only). This is slightly overdone.

                  I have written many custom validators (extends com.smartgwt.client.widgets.form.validator.CustomValidator) and they added with "setValidators" by any field. In this case also works setErrorMessage.

                  Now I would like to define the validators in the ...ds.xml unfortunately setErrorMessages remains without effect.

                  Can you please check whether it is a bug?

                  thanks,
                  timo

                  Comment


                    #10
                    In a client-side validator declared in the .ds.xml file you just set the errorMessage attribute (as in errorMessage="Must be before 2020").

                    If you think there's an issue here, please show a test case that we can run to see the issue.

                    Comment


                      #11
                      but the errormessage declared in ds.xml it's static and not dynamic, depending on the result.
                      You do not understand what I mean?

                      Comment


                        #12
                        The errorMessage is also dynamically evaluated client-side. Notice some of the built-in messages like "Must be exactly ${max} characters". You can do the same (eg "Must be less than ${validator.myProperty}" for a custom property "myProperty" that you placed on the field in .ds.xml.

                        Comment


                          #13
                          sorry....

                          my ds.xml
                          Code:
                           
                          <field hidden="false" required="false" detail="true" title="Instructing Agent" name="instgagtfininstnidbic" length="11" type="text">
                          	<validators>
                          		<validator type="ValidatorPacs008"
                          			errorMessage="must be ${validator.myProperty} before end !">
                          		</validator>
                          	</validators>
                          </field>
                          In my validator class, I tried the following, but all without success.
                          Class: ValidatorPacs008.java

                          setErrorMessage("my dynamic custom errormessage");
                          or/and
                          setAttribute("myProperty", "my dynamic custom errormessage");

                          On the screenshot you can see that it is not generated dynamically.

                          If this validator I assigns to each field (setValidators) individually it works, yes.
                          Attached Files

                          Comment


                            #14
                            You need to specify myProperty in the .ds.xml file. This handles your use case as you've explained it. If that's not your use case, start over, and show a test case for how you're trying to set the errorMessage dynamically.

                            Comment


                              #15
                              Hi Isomorphic,

                              here my test case, i extended your example builtinds.
                              please change employees.ds.xml and this EntryPoint class.

                              I changed your method bindComponents and insert "class myValidator".

                              Code:
                              package com.smartgwt.sample.client;
                              
                              import com.google.gwt.core.client.EntryPoint;
                              import com.smartgwt.client.core.KeyIdentifier;
                              import com.smartgwt.client.data.DataSource;
                              import com.smartgwt.client.data.Record;
                              import com.smartgwt.client.types.SelectionStyle;
                              import com.smartgwt.client.types.SortArrow;
                              import com.smartgwt.client.util.KeyCallback;
                              import com.smartgwt.client.util.Page;
                              import com.smartgwt.client.util.SC;
                              import com.smartgwt.client.widgets.IButton;
                              import com.smartgwt.client.widgets.Label;
                              import com.smartgwt.client.widgets.events.ClickEvent;
                              import com.smartgwt.client.widgets.events.ClickHandler;
                              import com.smartgwt.client.widgets.form.DynamicForm;
                              import com.smartgwt.client.widgets.form.validator.CustomValidator;
                              import com.smartgwt.client.widgets.form.validator.Validator;
                              import com.smartgwt.client.widgets.grid.ListGrid;
                              import com.smartgwt.client.widgets.grid.ListGridField;
                              import com.smartgwt.client.widgets.grid.ListGridRecord;
                              import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
                              import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
                              import com.smartgwt.client.widgets.layout.HLayout;
                              import com.smartgwt.client.widgets.layout.VStack;
                              import com.smartgwt.client.widgets.viewer.DetailViewer;
                              
                              /**
                               * Entry point classes define <code>onModuleLoad()</code>.
                               */
                              public class BuiltInDS implements EntryPoint {
                                  private ListGrid boundList;
                                  private DynamicForm boundForm;
                                  private IButton saveBtn;
                                  private DetailViewer boundViewer;
                                  private IButton newBtn;
                              
                                  
                                 
                                  /**
                                   * This is the entry point method.
                                   */
                                  public void onModuleLoad() {
                              		Validator.addValidatorDefinition("ValidatorPacs008", new myValidator());
                              
                                      KeyIdentifier debugKey = new KeyIdentifier();
                                      debugKey.setCtrlKey(true);
                                      debugKey.setKeyName("D");
                              
                                      Page.registerKey(debugKey, new KeyCallback() {
                                          public void execute(String keyName) {
                                              SC.showConsole();
                                          }
                                      });
                              
                                
                                      
                                      ListGrid grid = new ListGrid();
                                      grid.setLeft(20);
                                      grid.setTop(75);
                                      grid.setWidth(130);
                                      grid.setLeaveScrollbarGap(false);
                                      grid.setShowSortArrow(SortArrow.NONE);
                                      grid.setCanSort(false);
                                      grid.setFields(new ListGridField("dsTitle", "Select a DataSource"));
                                      grid.setData(new ListGridRecord[]{
                                              new DSRecord("Animals", "animals"),
                                              new DSRecord("Office Supplies", "supplyItem"),
                                              new DSRecord("Employees", "employees")}
                                      );
                                      grid.setSelectionType(SelectionStyle.SINGLE);
                                      grid.addRecordClickHandler(new RecordClickHandler() {
                                          public void onRecordClick(RecordClickEvent event) {
                                              DSRecord record = (DSRecord) event.getRecord();
                                              bindComponents(record.getDsName());
                                          }
                                      });
                              
                                      grid.draw();
                              
                                      VStack vStack = new VStack();
                                      vStack.setLeft(175);
                                      vStack.setTop(75);
                                      vStack.setWidth("70%");
                                      vStack.setMembersMargin(20);
                              
                                      Label label = new Label();
                                      label.setContents("<ul>" +
                                              "<li>select a datasource from the list at left to bind to these components</li>" +
                                              "<li>click a record in the grid to view and edit that record in the form</li>" +
                                              "<li>click <b>New</b> to start editing a new record in the form</li>" +
                                              "<li>click <b>Save</b> to save changes to a new or edited record in the form</li>" +
                                              "<li>click <b>Clear</b> to clear all fields in the form</li>" +
                                              "<li>click <b>Filter</b> to filter (substring match) the grid based on form values</li>" +
                                              "<li>click <b>Fetch</b> to fetch records (exact match) for the grid based on form values</li>" +
                                              "<li>double-click a record in the grid to edit inline (press Return, or arrow/tab to another record, to save)</li>" +
                                              "</ul>");
                                      vStack.addMember(label);
                              
                                      boundList = new ListGrid();
                                      boundList.setHeight(200);
                                      boundList.setCanEdit(true);
                              
                                      boundList.addRecordClickHandler(new RecordClickHandler() {
                                          public void onRecordClick(RecordClickEvent event) {
                                              Record record = event.getRecord();
                                              boundForm.editRecord(record);
                                              saveBtn.enable();
                                              boundViewer.viewSelectedData(boundList);
                                          }
                                      });
                                      vStack.addMember(boundList);
                              
                                      boundForm = new DynamicForm();
                                      boundForm.setNumCols(6);
                                      boundForm.setAutoFocus(false);
                                      vStack.addMember(boundForm);
                              
                                      HLayout hLayout = new HLayout(10);
                                      hLayout.setMembersMargin(10);
                                      hLayout.setHeight(22);
                              
                                      saveBtn = new IButton("Save");
                                      saveBtn.addClickHandler(new ClickHandler() {
                                          public void onClick(ClickEvent event) {
                                              boundForm.saveData();
                                              if (!boundForm.hasErrors()) {
                                                  boundForm.clearValues();
                                                  saveBtn.disable();
                                              }
                                          }
                                      });
                                      hLayout.addMember(saveBtn);
                              
                                      newBtn = new IButton("New");
                                      newBtn.addClickHandler(new ClickHandler() {
                                          public void onClick(ClickEvent event) {
                                              boundForm.editNewRecord();
                                              saveBtn.enable();
                                          }
                                      });
                                      hLayout.addMember(newBtn);
                              
                                      IButton clearBtn = new IButton("Clear");
                                      clearBtn.addClickHandler(new ClickHandler() {
                                          public void onClick(ClickEvent event) {
                                              boundForm.clearValues();
                                              saveBtn.disable();
                                          }
                                      });
                                      hLayout.addMember(clearBtn);
                              
                                      IButton filterBtn = new IButton("Filter");
                                      filterBtn.addClickHandler(new ClickHandler() {
                                          public void onClick(ClickEvent event) {
                                              boundList.filterData(boundForm.getValuesAsCriteria());
                                              saveBtn.disable();
                                          }
                                      });
                                      hLayout.addMember(filterBtn);
                              
                                      IButton fetchBtn = new IButton("Fetch");
                                      fetchBtn.addClickHandler(new ClickHandler() {
                                          public void onClick(ClickEvent event) {
                                              boundList.fetchData(boundForm.getValuesAsCriteria());
                                              saveBtn.disable();
                                          }
                                      });
                                      hLayout.addMember(fetchBtn);
                              
                                      vStack.addMember(hLayout);
                              
                                      boundViewer = new DetailViewer();
                                      vStack.addMember(boundViewer);
                              
                                      vStack.draw();
                                     
                                  }
                              
                                  private void bindComponents(String dsName) {
                                      DataSource ds = DataSource.get(dsName);
                                      
                                      boundList.setDataSource(ds);
                                      boundList.setUseAllDataSourceFields(true);
                                      
                                      boundForm.setDataSource(ds);
                                   
                              
                                  	
                                      if (dsName.equalsIgnoreCase("employees")){
                                      	
                              //set the CustomValidator manually at Filed Email
                              
                                  		ListGridField listGridField = new ListGridField("Email");
                                  		listGridField.setValidators(new myValidator());
                                  		boundList.setFields(listGridField);
                                  	
                                  		boundForm.getField("Email").setValidators(new myValidator());
                                      }
                                  	
                                      boundForm.setUseAllDataSourceFields(true);   
                                      
                                      
                                      
                                      //boundViewer.setDataSource(ds);
                              
                                      boundList.fetchData();
                                      newBtn.enable();
                                      saveBtn.disable();
                                  }
                              }
                              
                              class myValidator extends CustomValidator{
                              
                              	@Override
                              	protected boolean condition(Object value) {
                              
                              		System.out.println(value.toString());
                              		if (value.toString().startsWith("US")){
                              			setErrorMessage("only europe countrys allowed");
                              		}else{
                              			if (value.toString().startsWith("DE")){
                              				setErrorMessage("germany is not valid");
                              			}else{
                              				if (value.toString().startsWith("123")){
                              					setErrorMessage("are you sure?");
                              				}else
                              					return true;
                              			}
                              		}
                              		
                              		return false;
                              	}
                              	
                              }

                              Please choose datasource employees and type "DEtest" or "UStest" in field Title and Email, now you will see the different result. (look at screenshot's)

                              Since it is the same validator, I expect the same error messages.

                              Is it understandable?

                              regards,
                              timo
                              Attached Files

                              Comment

                              Working...
                              X