Announcement

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

    ComboBoxItem does validate, even though entered value should fail IsOneOfValidator

    SmartGwt LGPL
    6.1p
    Latest Nightly 01/18/2018 09:02 +0000

    Have a form with a ComboBoxItem.

    ComboBoxItem filters its optionDataSource using setPickListFilterCriteriaFunction. The filter function filters all records from optionDataSource but one record, which is the shown in the list of the comboBoxItem.

    ComboBoxItem also has IsOneOfValidator.

    Now enter a value from optionDataSource, which is being filtered out by PickListFilterCriteriaFunction (i.e. the value entered is not part of the list of values of the comboBoxItem).

    It is important, to actually type this value. In my test case below that value would be "testUser1".

    Then perform form.validate.

    Expected behaviour:
    comboBoxItem does not validate.

    Actual behaviour:
    form.validate returns true.

    What works though:
    If a value is entered into comboBoxItem, which is not in optionDataSource (neither filtered or unfiltered), form.validate returns false as expected.

    If the invalid value is set by code, e.g. "dynamicForm.setValue("someFieldName", "testUser1");", form.validate returns false as expected, too.

    Could you please look into this?

    TIA
    André

    Test Case:
    Code:
    package de.scai.smartgwttest.client.util;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.user.client.ui.RootPanel;
    import com.smartgwt.client.data.Criteria;
    import com.smartgwt.client.data.DataSource;
    import com.smartgwt.client.data.fields.DataSourceIntegerField;
    import com.smartgwt.client.data.fields.DataSourceTextField;
    import com.smartgwt.client.util.SC;
    import com.smartgwt.client.widgets.Button;
    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.fields.ComboBoxItem;
    import com.smartgwt.client.widgets.form.fields.FormItemCriteriaFunction;
    import com.smartgwt.client.widgets.form.fields.FormItemFunctionContext;
    import com.smartgwt.client.widgets.grid.ListGridRecord;
    import com.smartgwt.client.widgets.layout.Layout;
    import com.smartgwt.client.widgets.layout.VLayout;
    import de.scai.smartgwttest.client.Demo;
    
    import java.util.List;
    
    public class DemoTicket2045 implements Demo, EntryPoint {
    
        private Layout layoutRoot;
    
        /**
         * This is the entry point method.
         */
        public void onModuleLoad() {
            layoutRoot = new VLayout();
    
            RootPanel.get().add(layoutRoot);
    
            run();
        }
    
        public DemoTicket2045(Layout layout) {
            super();
            layoutRoot = layout;
        }
    
        private class UserDataSource extends DataSource {
            public static final String FIELDNAME_ID = "FIELD_id";
            public static final String FIELDNAME_USERNAME = "FIELD_userName";
    
            protected DataSourceIntegerField id = new DataSourceIntegerField(FIELDNAME_ID, "Id");
            protected DataSourceTextField userName = new DataSourceTextField(FIELDNAME_USERNAME, "Username");
    
            private UserDataSource() {
                super();
                id.setPrimaryKey(true);
                addField(id);
                addField(userName);
                setClientOnly(true);
                setCacheAllData(true);
                createRecord();
            }
    
            public void createRecord() {
                ListGridRecord listGridRecord = new ListGridRecord();
    
                final ListGridRecord[] data = new ListGridRecord[2];
                listGridRecord.setAttribute(FIELDNAME_ID, 1);
                listGridRecord.setAttribute(FIELDNAME_USERNAME, "testUser1");
                data[0] = listGridRecord;
    
                listGridRecord = new ListGridRecord();
                listGridRecord.setAttribute(FIELDNAME_ID, 2);
                listGridRecord.setAttribute(FIELDNAME_USERNAME, "testUser2");
                data[1] = listGridRecord;
    
                setCacheData(data);
            }
    
        }
    
        private class SomeDynamicForm extends DynamicForm {
            protected ComboBoxItem comboBoxItem = new ComboBoxItem("someFieldName", "Lookup Field");
            private UserDataSource userDataSource;
    
            protected void init() {
                userDataSource = new UserDataSource();
    
                comboBoxItem.setOptionDataSource(userDataSource);
                comboBoxItem.setDisplayField(UserDataSource.FIELDNAME_USERNAME);
                comboBoxItem.setValueField(UserDataSource.FIELDNAME_ID);
                comboBoxItem.setValidators(new com.smartgwt.client.widgets.form.validator.IsOneOfValidator());
                comboBoxItem.setPickListFilterCriteriaFunction(new FormItemCriteriaFunction() {
                    @Override
                    public Criteria getCriteria(FormItemFunctionContext formItemFunctionContext) {
                        java.util.List<com.smartgwt.client.data.Criterion> criterionList = new java.util.ArrayList<com.smartgwt.client.data.Criterion>();
                        criterionList.add(
                                new com.smartgwt.client.data.Criterion(
                                        UserDataSource.FIELDNAME_USERNAME,
                                        com.smartgwt.client.types.OperatorId.EQUALS,
                                        "testUser2"
                                )
                        );
                        return new com.smartgwt.client.data.AdvancedCriteria(
                                com.smartgwt.client.types.OperatorId.OR, criterionList.toArray(new com.smartgwt.client.data.Criterion[criterionList.size()])
                        );
                    }
                });
    
                this.setFields(
                        comboBoxItem
                );
            }
        }
    
        private SomeDynamicForm dynamicForm;
    
        public void run() {
    
            initForm();
    
            Layout layout = new VLayout();
            layoutRoot.addMember(layout);
    
            Label label = new Label("Type 'testUser1' into the comboxItem. It is not shown in the list of the " +
                    "comboBoxItem, so validate should return false");
    
            layout.addMember(label);
            layout.addMember(dynamicForm);
    
            Button buttonValidate = new Button("Validate");
            buttonValidate.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent clickEvent) {
                    SC.say("Validate OK: " + Boolean.toString(dynamicForm.validate()));
                }
            });
            layout.addMember(buttonValidate);
    
        }
    
        @Override
        public List<String> getRequiredEnvironmentInfo() {
            return null;
        }
    
        @Override
        public List<String> getFailedEnvironmentChecks() {
            return null;
        }
    
        private void initForm() {
            dynamicForm = new SomeDynamicForm();
            dynamicForm.init();
        }
    
    }

    #2
    Have you read the docs for the isOneOf validator? It requires a list of values, or for the item to have a valueMap. Neither has been satisfied in your test case.

    What you may be looking for instead is addUnknownValues:false.

    Comment


      #3
      Hmm, trying to dig through that. My assumption was, that providing an optionDataSource would be a similar thing.

      Looking at this https://www.smartclient.com/smartgwt.../ValueMap.html

      A ValueMap can be entirely static or entirely dynamic, with many options in between. For example, a ValueMap may be:
      • statically defined in a JavaScript or XML file. Such a valueMap changes only when application code is upgraded.
      • generated dynamically by server code when the application first loads, for example, by generating JavaScript or XML dynamically in a .jsp or .asp file. Such a valueMap may be different for each session and for each user.
      • loaded on demand from a DataSource, via the optionDataSource property, or via a call to DataSource.fetchData() where a valueMap is derived dynamically from the returned data (see DataSource.fetchData() for an example). Such a valueMap may be updated at any time, for example, every time the user changes a related field while editing data.
      So looking at the last bullet point, I assume that is what I did: Provided the valueMap by an optionDataSource.

      Nevertheless, you are perfectly right that adding setAddUnknownValues(false) changes the behaviour as desired.

      Maybe the docs in the IsOneOfValidator should get a hint, that addUnknownValues should be set to false when using the IsOneOf validator on a comboBoxItem. Having to set that, too (since addUnknownValues exists as a setting), is reasonable, but not obvious.

      So, for others, the following test case works as desired.
      Code:
      package de.scai.smartgwttest.client.tickets;
      
      import com.google.gwt.core.client.EntryPoint;
      import com.google.gwt.core.client.GWT;
      import com.google.gwt.user.client.ui.RootPanel;
      import com.smartgwt.client.data.Criteria;
      import com.smartgwt.client.data.DataSource;
      import com.smartgwt.client.data.fields.DataSourceIntegerField;
      import com.smartgwt.client.data.fields.DataSourceTextField;
      import com.smartgwt.client.util.SC;
      import com.smartgwt.client.widgets.Button;
      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.fields.ComboBoxItem;
      import com.smartgwt.client.widgets.form.fields.FormItemCriteriaFunction;
      import com.smartgwt.client.widgets.form.fields.FormItemFunctionContext;
      import com.smartgwt.client.widgets.grid.ListGridRecord;
      import com.smartgwt.client.widgets.layout.Layout;
      import com.smartgwt.client.widgets.layout.VLayout;
      
      
      import java.util.List;
      
      public class DemoTicket2045_AddUnknownValues implements EntryPoint {
      
          private Layout layoutRoot;
      
          /**
           * This is the entry point method.
           */
          public void onModuleLoad() {
              layoutRoot = new VLayout();
      
              RootPanel.get().add(layoutRoot);
      
              run();
          }
      
          private class UserDataSource extends DataSource {
              public static final String FIELDNAME_ID = "FIELD_id";
              public static final String FIELDNAME_USERNAME = "FIELD_userName";
      
              protected DataSourceIntegerField id = new DataSourceIntegerField(FIELDNAME_ID, "Id");
              protected DataSourceTextField userName = new DataSourceTextField(FIELDNAME_USERNAME, "Username");
      
              private UserDataSource() {
                  super();
                  id.setPrimaryKey(true);
                  addField(id);
                  addField(userName);
                  setClientOnly(true);
                  setCacheAllData(true);
                  createRecord();
              }
      
              public void createRecord() {
                  ListGridRecord listGridRecord = new ListGridRecord();
      
                  final ListGridRecord[] data = new ListGridRecord[2];
                  listGridRecord.setAttribute(FIELDNAME_ID, 1);
                  listGridRecord.setAttribute(FIELDNAME_USERNAME, "testUser1");
                  data[0] = listGridRecord;
      
                  listGridRecord = new ListGridRecord();
                  listGridRecord.setAttribute(FIELDNAME_ID, 2);
                  listGridRecord.setAttribute(FIELDNAME_USERNAME, "testUser2");
                  data[1] = listGridRecord;
      
                  setCacheData(data);
              }
      
          }
      
          private class SomeDynamicForm extends DynamicForm {
              protected ComboBoxItem comboBoxItem = new ComboBoxItem("someFieldName", "Lookup Field");
              private UserDataSource userDataSource;
      
              protected void init() {
                  userDataSource = new UserDataSource();
      
                  comboBoxItem.setOptionDataSource(userDataSource);
                  comboBoxItem.setDisplayField(UserDataSource.FIELDNAME_USERNAME);
                  comboBoxItem.setValueField(UserDataSource.FIELDNAME_ID);
                  comboBoxItem.setAddUnknownValues(false);
                  comboBoxItem.setValidators(new com.smartgwt.client.widgets.form.validator.IsOneOfValidator());
                  comboBoxItem.setPickListFilterCriteriaFunction(new FormItemCriteriaFunction() {
                      @Override
                      public Criteria getCriteria(FormItemFunctionContext formItemFunctionContext) {
                          GWT.log("getCriteria");
                          List<com.smartgwt.client.data.Criterion> criterionList = new java.util.ArrayList<com.smartgwt.client.data.Criterion>();
                          criterionList.add(
                                  new com.smartgwt.client.data.Criterion(
                                          UserDataSource.FIELDNAME_USERNAME,
                                          com.smartgwt.client.types.OperatorId.EQUALS,
                                          "testUser2"
                                  )
                          );
                          return new com.smartgwt.client.data.AdvancedCriteria(
                                  com.smartgwt.client.types.OperatorId.OR, criterionList.toArray(new com.smartgwt.client.data.Criterion[criterionList.size()])
                          );
                      }
                  });
      
                  this.setFields(
                          comboBoxItem
                  );
              }
          }
      
          private SomeDynamicForm dynamicForm;
      
          public void run() {
              GWT.log("run");
      
              initForm();
      
              Layout layout = new VLayout();
              layoutRoot.addMember(layout);
      
              Label label = new Label("Type 'testUser1' into the comboxItem. It is not shown in the list of the " +
                      "comboBoxItem, so validate should return false");
      
              layout.addMember(label);
              layout.addMember(dynamicForm);
      
              Button buttonValidate = new Button("Validate");
              buttonValidate.addClickHandler(new ClickHandler() {
                  @Override
                  public void onClick(ClickEvent clickEvent) {
                      SC.say("Validate OK: " + Boolean.toString(dynamicForm.validate()));
                  }
              });
              layout.addMember(buttonValidate);
      
          }
      
          private void initForm() {
              dynamicForm = new SomeDynamicForm();
              dynamicForm.init();
          }
      
      }
      I still have another problem with the IsOneOf validator and setPickListFilterCriteriaFunction but I'm still working on putting this into a test case. So maybe I will follow up on this.

      Thank you!

      Comment


        #4
        There is an analogy between optionDataSource and valueMap made in the docs, but setting the optionDataSource property is not the same thing as setting the valueMap property. For one, the client does not even load all the values from the optionDataSource, so cannot perform a synchronous validation. See also the server-side isUnique and hasRelatedRecord validators, which are typically used when validating against a large dataset.

        addUnknownValues is not required to be set with an isOneOf validator and the different behaviors that you can get are useful, and not redundant.

        Comment


          #5
          I see. The issue that not all records might have been fetched using the optionDataSource property, is surely different from the behaviour of a valueMap. And without having all the data client side validation is not possible for the IsOneOf validator.

          Thank you for pointing that out.

          I also understand that the docs are stating, what is required. And I also understand that listing every arbitrary constellation of settings which do not work, makes the docs unreadable and useless. Non working constellations should only be mentioned for the most common pitfalls and misunderstandings. I do not know if that is the case here. Given that I did not find any other post on that, it does not seem very common, but only you can judge on that.

          I for myself understood it now ;)

          Thank you!

          Comment


            #6
            I have one more question:
            If an optionDataSource does not do the same thing as the valueMap in regard to the IsOneOf validator, I wonder if I can still have a complex pickListFilterCriteriaFunction limiting the result set of valid values and at the same time have the IsOneOf-validator working on that filtered list of values?

            Use Case:
            The optionDataSource of my ComboBoxItem has multiple columns, one column as the valueField, one column as the displayField, other columns are required for filter criteria determined in the pickListFilterCriteriaFunction. If I cannot use the optionDataSource but have to use a valueMap, can I supply a "valueMap with multiple columns per entry" on which the pickListFilterCriteriaFunction would work? Is there such a thing as a multi column valueMap at all?

            It seems as if these concepts would not work together.

            Currently I have a workaround using a custom validator, which works, but I of course want to stay close to the "default functionality".

            Comment


              #7
              An optionDataSource works as a "multi-column valueMap". If you have the data locally you can just create a clientOnly DataSource to use as your optionDataSource.

              An isOneOf validator is valid to use with an optionDataSource, but as we've pointed out just now, the docs indicate that you need to provide it a list of valid values. You can do this if the data is local, as described above. Or, if the list is dynamic, a custom validator is the way to go.

              Comment

              Working...
              X