Announcement

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

    FilterBuilder RelativeDateItem custom date format not displaying

    v11.1p_2020-07-15/Pro Deployment (built 2020-07-15)

    I am having a problem rendering an absolute date value in a FilterBuilder RelativeDateItem that has a custom date format applied. My preferred date format is yyyyMMdd consistently across both client and server. I am able to get the client to display and transmit this format to the server, but when it comes back from the server (e.g. load a saved filter) the value is in the RPC response, but does not render in the client. The field appears empty.

    I've tried setFormat("yyyyMMdd"), setInputFormat("yyyyMMdd") and various combinations of DateUtil.setDateInputFormat("YMD") and DateUtil.setShortDateDisplayFormatter(...) with no success.

    The following code snippet is the closest I've come to making an absolute date value render consistently in the client when sent from the server, but it uses in-built formatters which are no good in my situation.

    Code:
    // Code that displays a date from server using TOJAPANSHORTDATE
    final RelativeDateItem defaults = new RelativeDateItem();
    defaults.setAllowAbsoluteDates(true);
    defaults.setDateFormatter(DateDisplayFormat.TOJAPANSHORTDATE);
    RelativeDateItem.setDefaultProperties(defaults);
    Code:
    filterJson:"{\n \"_constructor\":\"AdvancedCriteria\", \n \"operator\":\"and\", \n \"criteria\":[\n {\n \"fieldName\":\"My_Field\", \n \"operator\":\"equals\", \n \"value\":\"2021-08-31\"\n }\n ]\n}"
        },
    This formats the date in the client as yyyy/MM/dd and transmits the date value to server as yyyy-MM-dd. When loaded from the server, the value is yyyy-MM-dd format and is rendered in the client as yyyy/MM/dd. I presume the conversion to yyyy-MM-dd is significant here...?

    If I change the code to set the custom format it doesn't work.

    Code:
    final RelativeDateItem defaults = new RelativeDateItem();
    defaults.setAllowAbsoluteDates(true);
    defaults.setFormat("yyyyMMdd");
    RelativeDateItem.setDefaultProperties(defaults);
    Code:
    filterJson:"{\n \"_constructor\":\"AdvancedCriteria\", \n \"operator\":\"and\", \n \"criteria\":[\n {\n \"fieldName\":\"My_Field\", \n \"operator\":\"equals\", \n \"value\":\"20210831\"\n }\n ]\n}"
        },
    I presume this is because the client cannot understand my format and render it as a date? But what am I missing?

    #2
    Please read the Date and Time Format and Storage overview in the reference.

    In a nutshell, you are setting various properties that control how dates are displayed and parsed from
    user input, but these have no effect on the network transmission of dates, which always uses the XML schema format.

    If, for whatever reason, you want to use a different on-the-wire representation of dates than the standard one (we wouldn’t recommend this), you can do so via implementing DataSource.transformRequest and transformResponse.

    Comment


      #3
      Thanks for the pointers.

      I read https://www.smartclient.com/smartgwt...ndStorage.html and it does clearly say

      When sent or received in XML or JSON, date field values should be serialized in the XML Schema date format - YYYY-MM-DD - are expected to be received in the same format.
      Ok, that makes sense, client and server both need a common format to understand that the value is a "date". That format is the XML Schema Date format YYYY-MM-DD

      So this leads to another problem. It seems perfectly valid to call setFormat("yyyyMMdd") in the client. But this appears to change the format of both the client display AND the value sent to the server. Should it?

      e.g.

      Code:
      final RelativeDateItem defaults = new RelativeDateItem();
      defaults.setAllowAbsoluteDates(true);
      defaults.setFormat("yyyyMMdd");
      RelativeDateItem.setDefaultProperties(defaults);
      sends this across the wire

      Code:
      data:{
              ...
              filterJson:"{\n \"_constructor\":\"AdvancedCriteria\", \n \"operator\":\"and\", \n \"criteria\":[\n {\n \"fieldName\":\"My_Field\", \n \"operator\":\"equals\", \n \"value\":\"20210831\"\n }\n ]\n}"
          },
      Just in case I was misinterpreting the javadoc I removed setFormat() completely and tried some more of the built-in formatters.

      Code:
      final RelativeDateItem defaults = new RelativeDateItem();
      defaults.setAllowAbsoluteDates(true);
      defaults.setDateFormatter(DateDisplayFormat.TOJAPANSHORTDATE);
      RelativeDateItem.setDefaultProperties(defaults);
      and
      Code:
      final RelativeDateItem defaults = new RelativeDateItem();
      defaults.setAllowAbsoluteDates(true);
      defaults.setDateFormatter(DateDisplayFormat.TOEUROPEANSHORTDATE);
      RelativeDateItem.setDefaultProperties(defaults);
      and
      Code:
      final RelativeDateItem defaults = new RelativeDateItem();
      defaults.setAllowAbsoluteDates(true);
      defaults.setDateFormatter(DateDisplayFormat.TOUSSHORTDATE);
      RelativeDateItem.setDefaultProperties(defaults);
      Each of these formatters works as expected client-side and do translate the client-side date to a YYYY-MM-DD value in the RPC request, but ONLY the TOJAPANSHORTDATE formatter seems to display the value when it's sent back from the server as YYYY-MM-DD, the other two do not. I just get an empty widget. Based on your reply, I'd expect any of the built-in formatters to display a date if the response is in YYYY-MM-DD format? Am I missing something else?

      Comment


        #4
        v11.1p_2021-06-29/Pro Deployment (built 2021-06-29)

        Further to this, I feel like there's a bug here. The behaviour for rendering dates is different depending on the DateDisplayFormat applied in the client. Here is a test case demonstrating the behaviour using the latest nightly that I have access to.

        Select "The Date" field, leave "equals" and pick a fixed date from the date-picker.
        Click Save.
        Observe the JSON generated.
        Click Restore.
        Notice the empty value field where the date should be.

        If you change
        defaults.setDisplayFormat(DateDisplayFormat.TOEUROPEANSHORTDATE);
        to
        defaults.setDisplayFormat(DateDisplayFormat.TOJAPANSHORTDATE);
        and repeat the test, the date is displayed correctly after Restore.

        I can't see any obvious reason for this behaviour. Why does JAPANSHORTDATE work and other DateDisplayFormats do not?

        Code:
        import com.google.gwt.core.client.EntryPoint;
        import com.google.gwt.core.client.GWT;
        import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
        import com.smartgwt.client.core.KeyIdentifier;
        import com.smartgwt.client.data.AdvancedCriteria;
        import com.smartgwt.client.data.DataSource;
        import com.smartgwt.client.data.DataSourceField;
        import com.smartgwt.client.data.Record;
        import com.smartgwt.client.types.DateDisplayFormat;
        import com.smartgwt.client.types.FieldType;
        import com.smartgwt.client.types.TimeUnit;
        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.events.ClickEvent;
        import com.smartgwt.client.widgets.events.ClickHandler;
        import com.smartgwt.client.widgets.form.FilterBuilder;
        import com.smartgwt.client.widgets.form.fields.RelativeDateItem;
        import com.smartgwt.client.widgets.layout.VStack;
        
        /**
         * Entry point classes define <code>onModuleLoad()</code>.
         */
        public class CustomDS implements EntryPoint {
        
            private String filterString;
        
            /**
             * This is the entry point method.
             */
            public void onModuleLoad() {
        
                GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
        
                    @Override
                    public void onUncaughtException(Throwable arg0) {
                        SC.warn("Uncaught exception: " + arg0);
                    }
                });
        
                final KeyIdentifier debugKey = new KeyIdentifier();
                debugKey.setCtrlKey(true);
                debugKey.setKeyName("D");
        
                Page.registerKey(debugKey, new PageKeyHandler() {
                    public void execute(String keyName) {
                        SC.showConsole();
                    }
                });
        
                final DataSourceField name = new DataSourceField("name", FieldType.TEXT);
                name.setPrimaryKey(true);
                final DataSourceField title = new DataSourceField("title", FieldType.TEXT);
                final DataSourceField type = new DataSourceField("type", FieldType.TEXT);
                final DataSourceField length = new DataSourceField("length", FieldType.TEXT);
        
                final DataSource ds = new DataSource();
                ds.setClientOnly(true);
                ds.setFields(name, title, type, length);
        
                final Record record = new Record();
                record.setAttribute("name", "theDate");
                record.setAttribute("title", "The Date");
                record.setAttribute("type", "date");
                record.setAttribute("length", "10");
                ds.setTestData(record);
        
                // Enforce how RelativeDateItem is rendered in FilterBuilder
                final RelativeDateItem defaults = new RelativeDateItem();
                defaults.setTimeUnitOptions(TimeUnit.DAY);
                defaults.setAllowAbsoluteDates(true);
                defaults.setDisplayFormat(DateDisplayFormat.TOEUROPEANSHORTDATE);
                RelativeDateItem.setDefaultProperties(defaults);
        
                final FilterBuilder filters = new FilterBuilder();
                filters.setFieldDataSource(ds);
        
                final IButton save = new IButton("Save");
                save.addClickHandler(new ClickHandler() {
        
                    @Override
                    public void onClick(ClickEvent event) {
                        filterString = filters.getCriteria().toJSON();
                        SC.say("JSON<br>" + filterString);
                        filters.clearCriteria();
                    }
                });
        
                final IButton restore = new IButton("Restore");
                restore.addClickHandler(new ClickHandler() {
        
                    @Override
                    public void onClick(ClickEvent event) {
                        if (filterString != null) {
                            filters.setCriteria(AdvancedCriteria.fromJSON(filterString));
        
                        }
                    }
                });
        
                final VStack stack = new VStack();
                stack.addMember(filters);
                stack.addMember(save);
                stack.addMember(restore);
                stack.draw();
            }
        
        }

        Comment


          #5
          In general, display settings do not affect how a DataSource serializes dates - the XML Schema format is always used, unless you implement your own serialization.

          For parsing responses from the server, again XML Schema format is assumed. However, JSON only allows Strings, and the only way your JSON string is going to become a true Date is if you have declared a field of type "date"/"datetime"/"time" and the field name / valueXPath matches the data. Otherwise, the value intended as a date remains a String, and many things will go wrong (formatting will not apply, etc).

          As far as settings formats for localization, you should just be loading the appropriate locale (see Internationalization overview) and then there is no need to set date formats at a per-component level. If you want to override the default date formats we set in the locale files because you find one that's wrong, you can just submit a change to the locale files (see this sticky thread).

          If, for whatever reason, you wanted to apply a system-wide date format that is different from standard locales, you would do that via DateUtil APIs so it's system-wide.

          We'll check on what's wrong with the Japanese formatter setting, but it's probably something like a bad constant, and this whole subsystem has been obsoleted long ago by locales, and also FormatString (see docs) as a better way of defining formats.

          Comment


            #6
            Actually, the test case is just bad usage, see the docs for toJSON():

            Code:
            Gets a JSON encoding of an AdvancedCriteria object. Note that dates will not round-trip perfectly because JSON has no way of representing date values, nor a method of differentiating between dates, times and datetimes. To have dates, times and datetimes round-trip correctly, use asString() and fromString(), which serialize via the LOGICAL_DATE_CONSTRUCTOR mode of JSONEncoder.
            So what you are seeing is simply that dates are not round-tripping correctly as actual date objects - they end up as just strings. Because they are just strings, formatting does not apply. You will find that no formatting options have any effect at this point - it's not just the Japan formatter. The reason why the Euro short date formatter appears to work is because it just happens to be the same as the default JSON string representation of dates.

            Comment


              #7
              Thank you for the explanation. I changed the code and it works as expected using different client-side DateFormatters.

              Code:
              filterString = filters.getCriteria().asString();
              and

              Code:
              filters.setCriteria(AdvancedCriteria.fromString(filterString));
              For date 8/31/2021 creates

              Code:
              { "_constructor":"AdvancedCriteria", "operator":"and", "criteria":[ { "fieldName":"theDate", "operator":"equals", "value":isc.DateUtil.createLogicalDate(2021, 7, 31) } ] }
              Month is shifted to "7" due to using zero-based months for java.util.GregorianCalendar which is what DateUtil seems to use.

              Last question - bearing in mind we need to persist these filters, potentially indefinitely (our product relies on using saved filters), what is the likelihood of this asString() / fromString() API changing in a future upgrade?

              Comment


                #8
                The API won't change, but bear in mind we are "deserializing" that JS via a JavaScript eval(). So you don't want to allow untrusted users to define criteria for other users, or they could inject script.

                We'll check on the date shift - that's surprising as there are lots of automated tests in this area.

                Comment


                  #9
                  To be clear, I don't think there's anything wrong with the date shift for the month number. The DateUtil class appears to use java.util.GregorianCalendar API underneath, which is expecting a zero-based index for month, not the month number. So "7" translates (correctly) to August. Year and day are just their respective numbers.

                  Comment


                    #10
                    Ah, so just to confirm, when you serialization and deserialize via asString() and fromString(), there is no shift in the date value - you were just commenting that the serialization format happens to use zero-based months?

                    Comment


                      #11
                      Correct.

                      Comment

                      Working...
                      X