Announcement

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

  • 12.0p Validator.dependentFields unclear docs and effect

    Hi Isomorphic,

    the docs for Validator.dependentFields are pretty unclear on the effect of the attribute.
    Here I assumed that it might be a more declarative way to validate dependent fields, if only one field changes. But after testing it, this seems not to be true.
    As the docs are unclear to me, could you please explain what it actually does? I had two assumptions:
    1. When using a serverCustom validator, these fields are always sent with the validate or update request, so that they are available in
      Code:
      public boolean condition(Object value, Validator validator, String fieldName, @SuppressWarnings("rawtypes") Map record, DataSource ds,
      	            HttpServletRequest httpServletRequest)
    2. When a dependentField changes, but not the field where this validator is defined, this validator is run nevertheless.
    Both assumptions seem to be wrong.

    I want to make my configuration GUI a bit more bulletproof for misconfigurations and this would greatly benefit from such a feature and/or applyWhen (which seems to be working IMHO).
    I'm using latest 12.0p here.

    Best regards
    Blama

  • #2
    Behavior is as doc'd, it allows you to have a validator run when there has been a change in another field.

    If you think it's not working as doc'd, please show how to reproduce the issue.

    Comment


    • #3
      Hi Isomorphic,

      please see the attached BuiltInDS-based testcase (v12.0p_2018-12-13).
      As you can see, the serverCustom validator on itemName is only called for changes of itemName directly, not for changes of either unitCost or inStock.
      Also having just inStock as attribute value does not change anything.

      BuiltInDS.java:
      Code:
      package com.smartgwt.sample.client;
      
      import com.google.gwt.core.client.EntryPoint;
      import com.smartgwt.client.Version;
      import com.smartgwt.client.core.KeyIdentifier;
      import com.smartgwt.client.data.AdvancedCriteria;
      import com.smartgwt.client.data.Criterion;
      import com.smartgwt.client.data.DataSource;
      import com.smartgwt.client.data.SortSpecifier;
      import com.smartgwt.client.types.OperatorId;
      import com.smartgwt.client.types.SortDirection;
      import com.smartgwt.client.util.Page;
      import com.smartgwt.client.util.PageKeyHandler;
      import com.smartgwt.client.util.SC;
      import com.smartgwt.client.widgets.IButton;
      import com.smartgwt.client.widgets.Window;
      import com.smartgwt.client.widgets.events.ClickEvent;
      import com.smartgwt.client.widgets.events.ClickHandler;
      import com.smartgwt.client.widgets.grid.ListGrid;
      import com.smartgwt.client.widgets.grid.ListGridField;
      import com.smartgwt.client.widgets.layout.VLayout;
      
      public class BuiltInDS implements EntryPoint {
          private VLayout mainLayout;
          private IButton recreateBtn;
      
          public void onModuleLoad() {
              KeyIdentifier debugKey = new KeyIdentifier();
              debugKey.setCtrlKey(true);
              debugKey.setKeyName("D");
      
              Page.registerKey(debugKey, new PageKeyHandler() {
                  public void execute(String keyName) {
                      SC.showConsole();
                  }
              });
      
              mainLayout = new VLayout(20);
              mainLayout.setWidth100();
              mainLayout.setHeight100();
      
              recreateBtn = new IButton("Recreate");
              recreateBtn.addClickHandler(new ClickHandler() {
                  @Override
                  public void onClick(ClickEvent event) {
                      recreate();
                  }
              });
              mainLayout.addMember(recreateBtn);
              recreate();
              mainLayout.draw();
          }
      
          private void recreate() {
              Window w = new Window();
              w.setWidth("95%");
              w.setHeight("95%");
              w.setMembersMargin(0);
              w.setModalMaskOpacity(70);
              w.setTitle(" (" + Version.getVersion() + "/" + Version.getSCVersionNumber() + ")");
              w.setTitle("TITLE" + w.getTitle());
              w.setShowMinimizeButton(false);
              w.setIsModal(true);
              w.setShowModalMask(true);
              w.centerInPage();
      
              final ListGrid supplyItemGrid = new ListGrid(DataSource.get("supplyItem"));
              supplyItemGrid.setHeight100();
              supplyItemGrid.setWidth100();
              supplyItemGrid.setCanEdit(true);
              supplyItemGrid.setAutoFetchData(false);
      
              ListGridField itemID = new ListGridField("itemID");
              ListGridField itemName = new ListGridField("itemName");
              ListGridField sku = new ListGridField("SKU");
              ListGridField category = new ListGridField("category");
              ListGridField units = new ListGridField("units");
              ListGridField unitCost = new ListGridField("unitCost");
              ListGridField inStock = new ListGridField("inStock");
              ListGridField nextShipment = new ListGridField("nextShipment");
      
              supplyItemGrid.setFields(itemID, itemName, sku, category, units, unitCost, inStock, nextShipment);
              supplyItemGrid.setSort(new SortSpecifier[] { new SortSpecifier(itemName.getName(), SortDirection.ASCENDING) });
              supplyItemGrid.fetchData(new AdvancedCriteria(new Criterion(itemName.getName(), OperatorId.STARTS_WITH, "A")));
              w.addItem(supplyItemGrid);
              w.show();
          }
      }
      TestValidator.java:
      Code:
      package com.smartgwt.sample.server.listener;
      
      import java.util.Map;
      
      import javax.servlet.http.HttpServletRequest;
      
      import com.isomorphic.datasource.DSResponse;
      import com.isomorphic.datasource.DataSource;
      import com.isomorphic.datasource.Validator;
      import com.isomorphic.log.Logger;
      
      public class TestValidator {
          Logger log = new Logger(DSResponse.class.getName());
      
          public boolean condition(Object value, Validator validator, String fieldName, @SuppressWarnings("rawtypes") Map record, DataSource ds,
                  HttpServletRequest httpServletRequest) throws Exception {
              log.info("TestValidator called");
              return true;
          }
      }
      supplyItem.ds.xml:
      Code:
      <DataSource
          ID="supplyItem"
          serverType="sql"
          tableName="supplyItem"
          titleField="itemName"
          testFileName="/examples/shared/ds/test_data/supplyItem.data.xml"
          dbImportFileName="/examples/shared/ds/test_data/supplyItemLarge.data.xml"
      >
          <fields>
              <field name="itemID"      type="sequence" hidden="true"       primaryKey="true"/>
              <field name="itemName"    type="text"     title="Item"        length="128"       required="true">
                        <validators>
                              <validator type="serverCustom" dependentFields="inStock, unitCost">
                                  <serverObject lookupStyle="new" className="com.smartgwt.sample.server.listener.TestValidator" />
                                  <errorMessage>$errorMessage</errorMessage>
                              </validator>
                          </validators>
              </field>
              <field name="SKU"         type="text"     title="SKU"         length="10"        required="true"/>
              <field name="description" type="text"     title="Description" length="2000"/>
              <field name="category"    type="text"     title="Category"    length="128"       required="true"
                     foreignKey="supplyCategory.categoryName"/>
              <field name="units"       type="enum"     title="Units"       length="5">
                  <valueMap>
                      <value>Roll</value>
                      <value>Ea</value>
                      <value>Pkt</value>
                      <value>Set</value>
                      <value>Tube</value>
                      <value>Pad</value>
                      <value>Ream</value>
                      <value>Tin</value>
                      <value>Bag</value>
                      <value>Ctn</value>
                      <value>Box</value>
                  </valueMap>
              </field>
              <field name="unitCost"    type="float"    title="Unit Cost"   required="true">
                  <validators>
                      <validator type="floatRange" min="0" errorMessage="Please enter a valid (positive) cost"/>
                      <validator type="floatPrecision" precision="2" errorMessage="The maximum allowed precision is 2"/>
                  </validators>
              </field>
              <field name="inStock"   type="boolean"  title="In Stock"/>
              <field name="nextShipment"  type="date" title="Next Shipment"/>
          </fields>
      </DataSource>
      Best regards
      Blama

      Comment


      • #4
        Hi Isomorphic,

        this means the idea is what I described as 2nd point in #1.
        Can you log the 1st point in #1 as enhancement? It would help to solve more easy cases of multi-field validation, which otherwise would require a SQLDataSource subclass and an @override of validateRecord(), like you describe here.

        Please disregard the "missing docs" point I made here earlier. I just saw that it is in the parent class.

        Best regards
        Blama
        Last edited by Blama; 17th Dec 2018, 09:44.

        Comment


        • #5
          Validators can be written that are dependent on other fields and should be triggered on changes to them as well. So in your case you are saying that the serverCustom validator on itemName should be rechecked on changes to inStock and unitCost in addition to direct itemName changes. For the serverCustom validator and server validators in general the entire record is sent to the server so your 1st point is covered.

          Note that server validators are not triggered from immediate field changes by default because they are asynchronous and could take substantial time to return. Therefore in your test case the serverCustom validator will not trigger until saving the row. This can be overridden by setting validateOnChange on the validator.

          Finally, the specification of dependentFields in the datasource isn't correct. It takes an array of strings so use:

          Code:
          <dependentFields>
              <dependentField>inStock</dependentField>
              <dependentField>unitCost</dependentField>
          </dependentFields>

          Comment


          • #6
            Hi Isomorphic,

            1.Regarding this in #5:
            Note that server validators are not triggered from immediate field changes by default because they are asynchronous and could take substantial time to return. Therefore in your test case the serverCustom validator will not trigger until saving the row. This can be overridden by setting validateOnChange on the validator.
            I used the above code from Blama (#3) and modified the supplyItem.ds.xml file like You said in #5 to the following:

            Code:
            <DataSource
                ID="supplyItem"
                serverType="sql"
                tableName="supplyItem"
                titleField="itemName"
                testFileName="/examples/shared/ds/test_data/supplyItem.data.xml"
                dbImportFileName="/examples/shared/ds/test_data/supplyItemLarge.data.xml"
            >
                <fields>
                    <field name="itemID"      type="sequence" hidden="true"       primaryKey="true"/>
                    <field name="itemName"    type="text"     title="Item"        length="128"       required="true">
                              <validators>
                                    <validator type="serverCustom" dependentFields="inStock, unitCost" validateOnChange="true">
                                        <serverObject lookupStyle="new" className="com.smartgwt.sample.server.listener.TestValidator" />
                                        <errorMessage>$errorMessage</errorMessage>
                                    </validator>
                                </validators>
                    </field>
                    <field name="SKU"         type="text"     title="SKU"         length="10"        required="true"/>
                    <field name="description" type="text"     title="Description" length="2000"/>
                    <field name="category"    type="text"     title="Category"    length="128"       required="true"
                           foreignKey="supplyCategory.categoryName"/>
                    <field name="units"       type="enum"     title="Units"       length="5">
                        <valueMap>
                            <value>Roll</value>
                            <value>Ea</value>
                            <value>Pkt</value>
                            <value>Set</value>
                            <value>Tube</value>
                            <value>Pad</value>
                            <value>Ream</value>
                            <value>Tin</value>
                            <value>Bag</value>
                            <value>Ctn</value>
                            <value>Box</value>
                        </valueMap>
                    </field>
                    <field name="unitCost"    type="float"    title="Unit Cost"   required="true">
                        <validators>
                            <validator type="floatRange" min="0" errorMessage="Please enter a valid (positive) cost"/>
                            <validator type="floatPrecision" precision="2" errorMessage="The maximum allowed precision is 2"/>
                        </validators>
                    </field>
                    <field name="inStock"   type="boolean"  title="In Stock"/>
                    <field name="nextShipment"  type="date" title="Next Shipment"/>
                </fields>
            </DataSource>
            As I understand right, You, as well as the docs https://www.smartclient.com/smartgwt...lidateOnChange, say, if one field in the row is changed a validation is triggered due to validateOnChange declared in the ds.xml file.
            In the above example, no validation is triggered if I change for example SKU. The only validation made here is, when I change the ItemName. Am I missing something here, or did I misunderstand the docs?

            2. If I extend the supplyItem.ds.xml with dependentFields like this:

            Code:
            <DataSource
                ID="supplyItem"
                serverType="sql"
                tableName="supplyItem"
                titleField="itemName"
                testFileName="/examples/shared/ds/test_data/supplyItem.data.xml"
                dbImportFileName="/examples/shared/ds/test_data/supplyItemLarge.data.xml"
            >
                <fields>
                    <field name="itemID"      type="sequence" hidden="true"       primaryKey="true"/>
                    <field name="itemName"    type="text"     title="Item"        length="128"       required="true">
                              <validators>
                                    <validator type="serverCustom" validateOnChange="true">
                                        <serverObject lookupStyle="new" className="com.smartgwt.sample.server.listener.TestValidator" />
                                        <errorMessage>$errorMessage</errorMessage>
                           <dependentFields>
                                        <dependentField>inStock</dependentField>
                                        </dependentFields>
                                    </validator>
                                </validators>
                    </field>
                    <field name="SKU"         type="text"     title="SKU"         length="10"        required="true"/>
                    <field name="description" type="text"     title="Description" length="2000"/>
                    <field name="category"    type="text"     title="Category"    length="128"       required="true"
                           foreignKey="supplyCategory.categoryName"/>
                    <field name="units"       type="enum"     title="Units"       length="5">
                        <valueMap>
                            <value>Roll</value>
                            <value>Ea</value>
                            <value>Pkt</value>
                            <value>Set</value>
                            <value>Tube</value>
                            <value>Pad</value>
                            <value>Ream</value>
                            <value>Tin</value>
                            <value>Bag</value>
                            <value>Ctn</value>
                            <value>Box</value>
                        </valueMap>
                    </field>
                    <field name="unitCost"    type="float"    title="Unit Cost"   required="true">
                        <validators>
                            <validator type="floatRange" min="0" errorMessage="Please enter a valid (positive) cost"/>
                            <validator type="floatPrecision" precision="2" errorMessage="The maximum allowed precision is 2"/>
                        </validators>
                    </field>
                    <field name="inStock"   type="boolean"  title="In Stock"/>
                    <field name="nextShipment"  type="date" title="Next Shipment"/>
                </fields>
            </DataSource>
            and also adjust the TestValidator.java to:

            Code:
            package com.smartgwt.sample.server.listener;
            
            import java.util.Map;
            
            import javax.servlet.http.HttpServletRequest;
            
            import com.isomorphic.datasource.DSResponse;
            import com.isomorphic.datasource.DataSource;
            import com.isomorphic.datasource.Validator;
            import com.isomorphic.log.Logger;
            
            public class TestValidator {
                Logger log = new Logger(DSResponse.class.getName());
            
                public boolean condition(Object value, Validator validator, String fieldName, @SuppressWarnings("rawtypes") Map record, DataSource ds,
                        HttpServletRequest httpServletRequest) throws Exception {
                    log.info("TestValidator called");
                    String valueName = (String) record.get("itemName");
                    if (!valueName.equals("Account Book 168 Page Hardcover A4 4MC")) {
                        validator.addErrorMessageVariable("errorMessage", "Error");
                        return false;
                    } else
                        return true;
                }
            }
            I can still check & uncheck "inStock" without validation. The Validator runs only, when the row is in edit-mode. I know, that i can use "setCanToggle" on the inStock ListGridField, but shouldn't it be validated anyway?


            3. With the setup from 2. I select a row by double-click. Then, I check/uncheck the dependentField "inStock" and click outside, to send the request. In the Developer Console, I first get an validation-error but then an successful update on that row. Is that behaviour wanted?

            Thanks in Advance!

            Kind Regards

            Comment


            • #7
              You must set validateOnChange on any fields where you want validation on change. Setting a field as a dependentField does not affect the setting of validateOnChange for those fields - it still has its default value of false.

              We’re unclear on what you’re reporting in the last item. If a validator fails there will not be an update. You should double-check what requests are being sent and with what data, and what your validator returns in each case. Let us know if you, after looking closely, you believe you’ve found a framework bug.

              Comment


              • #8
                Hi Isomorphic,

                Originally posted by Isomorphic View Post
                You must set validateOnChange on any fields where you want validation on change. Setting a field as a dependentField does not affect the setting of validateOnChange for those fields - it still has its default value of false.
                Here the setting was on the validator (see the bold text in the 1st code-block), which sets this on all fields per the docs.
                If true, validator will be validated when each item's "change" handler is fired as well as when the entire form is submitted or validated. If false, this validator will not fire on the item's "change" handler.

                Note that this property can also be set at the form/grid or field level; If true at any level and not explicitly false on the validator, the validator will be fired on change - displaying errors and rejecting the change on validation failure.
                Originally posted by Isomorphic View Post
                We're unclear on what you’re reporting in the last item. If a validator fails there will not be an update. You should double-check what requests are being sent and with what data, and what your validator returns in each case. Let us know if you, after looking closely, you believe you've found a framework bug.
                That is good to hear. Are you saying an update via ListGrid editing will/should be like this:
                1. Check all clientSide validators
                2. On error, show validation error and stop
                3. Check all serverSide validators via one (or more?) validate-DSRequests
                4. Wait for the DSRequest to return
                5. On error, show validation errors and stop
                6. On success, issue Update-DSRequest (which will again run serverside validation logic)
                Best regards
                Blama

                Comment


                • #9
                  Docs are correct and agree with what we’ve told you. validateOnChange on a validator means validation will be triggered on change for any item where that validator is applied. It would obviously be wrong for validateOnChange on one validator on one field to effectively flip validateOnChange to true for all fields in the form.

                  Your flow is not correct. If there is an update and validators pass it is processed on the server immediately. There would be no reason to go back to the client first.

                  Comment


                  • #10
                    Hi Isomorphic,

                    w.r.t to the flow then there is a problem. In #5 you say:
                    Originally posted by Isomorphic View Post
                    For the serverCustom validator and server validators in general the entire record is sent to the server so your 1st point is covered.
                    If the flow is (is it correct that way?):
                    1. Check all clientSide validators
                    2. On error, show validation error and stop
                    3. On success, issue Update-DSRequest (which run serverside validation logic)
                    this results in the problem that the Update-DSRequest has way less data (only changed fields) than a potential Validate-DSRequest (which as you say has the full record).

                    Best regards
                    Blama

                    Comment


                    • #11
                      Both have complete data from the client via DSRequest.oldValues. But be careful of writing validators wrongly by relying on client data, which may be stale or spoofed.

                      Comment


                      • #12
                        Hi Isomorphic,

                        agreed. It's only annoying to have to write the "get DB data and merge" inside the every validator's server code.
                        Of course this is easy via inheritance but perhaps you can make this available in a validator server side base class for all users as an enhancement?

                        We'll report back if there is a problem from #6 left.

                        Best regards
                        Blama

                        Comment


                        • #13
                          Hi Isomorphic,

                          I created a new thread for a remaining issue of #6 here.

                          W.r.t. to the enhancement suggestion in #1/#4 and #12:
                          • I think that the one of #1/#4 is invalid as you are already doing this for validate requests (like you say in #5). Most likely can't do this for update requests (as this would result in these fields being updated as well (to their old/current value, but anyway)).
                          • The enhancement suggestion from #12 would be nice, but is very easy most likely. I'll report back with a code suggestion for a base class.
                          Best regards
                          Blama

                          Comment


                          • #14
                            Note that we already set up shortcut variables if you are using serverCondition with Velocity and/or Server Script (which would also make your test case much simpler - just a short snippet of Java/Groovy inside the .ds.xml file).

                            But the idea of also making the same functionality available to ordinary Java code is a good one, and we’’ll note it down.

                            Comment


                            • #15
                              Hi Isomorphic,

                              thanks. You are right and I do know about these. Velocity validator that are more complex than one line turned out to be "write only" and un-debuggable (=breakpoint step-though) for me.
                              Even if it's overhead in LOC I much more like the Java validators. I did not try server script so far. This would be un-debuggable (=breakpoint step-though) as well, correct?

                              Best regards
                              Blama

                              Comment

                              Working...
                              X