Announcement

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

    fetchData on client only datasource does not return the entire record

    SmartGWT v9.0p_2014-03-06/PowerEdition Deployment (built 2014-03-06)

    I have a ListGrid backed by a client only datasource. The datasource is loaded by calling setCacheData() since it will contain several thousand records that are built from three different datasources on the server.

    When I update a record by calling ds.updateData() with only a subset of the record's fields, any subsequent calls to ds.fetchData() for that record will only return what was sent in the update. However, if I invoke invalidateCache() on the ListGrid, it will load the ListGrid with the entire record.

    Should I update the ListGrid directly instead of trying to do this thru the data source? Or is there something I'm missing with regard to interacting with the client side datasource in this manner? I've created the test case below that demonstrates the problem.

    The first time you click the update button, the first fetch and the update will show the full record in the respective text areas. The second fetch (which occurs after the update) only shows the fields passed in the update record. Repeatedly clicking Update then shows that all calls to fetch only return the updated fields.

    The RecordUtil.recordToString() method iterates over all the attributes in the given record and separates them with EOL.
    Code:
        public Layout createTestLayout() {
            HLayout layout = new HLayout();
    
            final DataSource ds = new DataSource();
            ds.setID("clientDS");
            ds.setClientOnly(true);
    
            List<DataSourceField> dsFields = new ArrayList<DataSourceField>();
            DataSourceField f = new DataSourceField("id", FieldType.INTEGER);
            f.setPrimaryKey(true);
            dsFields.add(f);
            dsFields.add(new DataSourceField("a", FieldType.TEXT));
            dsFields.add(new DataSourceField("b", FieldType.TEXT));
            dsFields.add(new DataSourceField("c", FieldType.TEXT));
            ds.setFields(dsFields.toArray(new DataSourceField[] {}));
    
            List<ListGridField> lgFields = new ArrayList<ListGridField>();
            lgFields.add(new ListGridField("a", 20));
            lgFields.add(new ListGridField("b", 20));
            lgFields.add(new ListGridField("c", 20));
    
            final ListGrid grid = new ListGrid();
            grid.setDataSource(ds);
            grid.setAutoFetchData(true);
            grid.setWidth(400);
            grid.setHeight(400);
    
            List<ListGridRecord> data = new ArrayList<ListGridRecord>();
            for (int i = 1; i < 15; i++) {
                ListGridRecord r = new ListGridRecord();
                r.setAttribute("id", i);
                r.setAttribute("a", "aaaaaa" + i);
                r.setAttribute("b", "bbbbbbb" + i);
                r.setAttribute("c", "cccccc" + i);
                data.add(r);
            }
    
            grid.getDataSource().invalidateCache();
            grid.invalidateCache();
    
            ds.setCacheData(data.toArray(new ListGridRecord[] {}));
    
            DynamicForm form = new DynamicForm();
            List<FormItem> items = new ArrayList<FormItem>();
            final TextAreaItem taRec = new TextAreaItem("FetchedRecord1");
            taRec.setWidth(400);
            taRec.setHeight(100);
            items.add(taRec);
    
            final TextAreaItem taResp = new TextAreaItem("UpdateResponse");
            taResp.setWidth(400);
            taResp.setHeight(100);
            items.add(taResp);
    
            final TextAreaItem taRec2 = new TextAreaItem("FetchedRecord2");
            taRec2.setWidth(400);
            taRec2.setHeight(100);
            items.add(taRec2);
    
            form.setFields(items.toArray(new FormItem[] {}));
    
            final IButton btn = new IButton("Update");
            btn.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
    
                    ds.fetchData(new Criteria("id", "1"), new DSCallback() {
                        @Override
                        public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
                            Record r = dsResponse.getData()[0];
                            taRec.setValue(RecordUtil.recordToString(r));
    
                            Record updatedRecord = new Record();
                            updatedRecord.setAttribute("id", 1);
                            updatedRecord.setAttribute("a", "zzzzz");
    
                            ds.updateData(updatedRecord, new DSCallback() {
                                @Override
                                public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
                                    Record r = dsResponse.getData()[0];
                                    taResp.setValue(RecordUtil.recordToString(r));
    
                                    ds.fetchData(new Criteria("id", "1"), new DSCallback() {
                                        @Override
                                        public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
                                            Record r = dsResponse.getData()[0];
                                            taRec2.setValue(RecordUtil.recordToString(r));
    
                                            grid.invalidateCache();
                                        }
                                    });
                                }
                            });
                        }
                    });
                }
            });
    
            layout.addMember(grid);
            layout.addMember(form);
            layout.addMember(btn);
            return layout;
        }

    #2
    A record passed to updateData() must be complete, otherwise we cannot tell if fields are being added or removed. So if you are in some context where you only have partial data, just grab the rest of the unchanged attributes from the record in the ListGrid, or if it's possible for it not to be loaded, from the clientOnly DataSource via just using fetchData().

    Comment


      #3
      Thanks.

      We've been using this approach with server backed SQL datasources for a while without any issues. In many cases, we only have partial data, but we always have the primary key. The update record we create usually only contains the primary key and one other field.

      Is this partial update approach only a problem with client only datasources?

      In my sample code, the ListGrid refreshes with the full record from the client only datasource after invoking invalidateCache() on the ListGrid. The updateData() call returns the full record in the callback's DSResponse on each successive call. Why does the fetchData() only return the fields that were sent in the updateData() call?

      I just checked the RPC tab in the dev console and the fetch contains the a,b,c fields, but the Record returned in the callback only contains the a field. Here is the request/response as seen in the RPC tab.

      Code:
      DSRequest =
      {
          dataSource:"clientDS", 
          operationType:"fetch", 
          data:{
              id:"1"
          }, 
          showPrompt:true, 
          requestId:"clientDS$62726", 
          fallbackToEval:false, 
          lastClientEventThreadCode:"TMR0", 
          bypassCache:true, 
          clientOnly:true
      }
      
      Response = 
      [
          {
              id:1, 
              a:"zzzzz", 
              b:"bbbbbbb1", 
              c:"cccccc1"
          }
      ]
      Last edited by brad_c; 5 Aug 2014, 17:28.

      Comment


        #4
        Originally posted by brad_c View Post
        Thanks.

        We've been using this approach with server backed SQL datasources for a while without any issues. In many cases, we only have partial data, but we always have the primary key. The update record we create usually only contains the primary key and one other field.

        Is this partial update approach only a problem with client only datasources?
        No. Returning partial data is incorrect for all DataSources. See also operationBinding.cacheSyncOperation for more context on how this works with server-side DataSources.

        In my sample code, the ListGrid refreshes with the full record from the client only datasource after invoking invalidateCache() on the ListGrid. The updateData() call returns the full record in the callback's DSResponse on each successive call. Why does the fetchData() only return the fields that were sent in the updateData() call?
        We're not sure what fetchData() you're referring to here, but if you call updateData() with partial fields, the DataSource data will have been updated and all subsequent fetches against the DataSource will only return the record with partial fields.

        Comment


          #5
          Originally posted by Isomorphic View Post
          We're not sure what fetchData() you're referring to here, but if you call updateData() with partial fields, the DataSource data will have been updated and all subsequent fetches against the DataSource will only return the record with partial fields.
          All fetch/update calls are happening on the datasource. The RPC tab in the dev console for the DS fetch is showing all fields returned in the response. But the Record that is returned by dsResponse.getData() only contains the field that was in the update call.

          I will switch to using the correct approach of passing all fields in the updateData() call, but I was curious as to why the RPC tab shows all fields being returned by the fetchData() call, but the returned Record only contains the modified field.

          Also, in my example code, when the ListGrid.invalidateCache() is called, the ListGrid retrieves all fields from the datasource. So the datasource is obviously still holding all the fields, but when I calling ds.fetchData(), it doesn't return all the fields.

          To my earlier point about how we have been using this without issue with server backed datasources, I forgot that those datsources are only used to save data. The data being saved is retrieved from a different datasource that uses includeFrom to bring in the fields. So, it works, but we would run into problems in that view if we started retrieving data from those datasources.

          Comment


            #6
            Those results don't make sense, and we're not seeing anything of the kind. Best guess, either:

            1. you're getting confused between which requests and responses are which, so you're comparing RPC tab results to the wrong log output (shown in your text areas)

            OR

            2. you have code that actually modifies dsResponse.data - that's fine, but then the RPC tab will show your modifications

            Comment


              #7
              I agree that the results don't make sense, but every DSResponse in the RPC tab contains all fields for the record, so I'm not getting the request/response mixed up. The Record retrieved from dsResponse.getData() only contains the updated field.

              If you run my example and click the Update button multiple times, you will see all the data in the RPC tab, but the DataSource API is only returning the updated field.

              The method I provided in the original post is all the code you need to reproduce this issue - aside from the RecordUtil method that just iterates over Record attributes and prints them out for debugging. There is no code that modifies response data.

              I also tested this against v9.0p_2014-07-12/PowerEdition Deployment (built 2014-07-12) and it occurred in that version too.

              Comment


                #8
                Originally posted by Isomorphic View Post
                A record passed to updateData() must be complete, otherwise we cannot tell if fields are being added or removed. So if you are in some context where you only have partial data, just grab the rest of the unchanged attributes from the record in the ListGrid, or if it's possible for it not to be loaded, from the clientOnly DataSource via just using fetchData().
                Does this mean that a Record passed to DataSource.updateData() should always be complete?
                Should a Record passed to ListGrid.updateData() always be complete?

                Your showcase example http://www.smartclient.com/smartgwt/...rations_update creates a partial Record with the PK and the field being updated and invokes ListGrid.updateData().

                If partial record updates are acceptable for ListGrid and other DataBoundComponent objects, but not for DataSource objects, that is understandable. However, the DataSource.updateData() allows this but then exhibits unexpected behavior (only returning what was updated). If that is by design, then so be it, I'm trying to figure out what is possible via the DataSource and DataBoundComponent APIs.

                I didn't see any documentation indicating you must pass complete Records to updateData(), and I found this forum post which appears to be the same problem I am experiencing and was fixed back in 3.0
                http://forums.smartclient.com/showth...edata+complete

                Comment


                  #9
                  We were about to correct that - what we meant to say is that the server must return the complete record-as-saved in response to an update request, and not just partial fields. This includes calls to the client-side API updateCaches(), which effectively simulates a response from the server.

                  But a call to updateData() can indeed pass only the PK + the fields that the client wants to update.

                  So, the RPC tab should indeed be showing the full record as you're seeing, and calling invalidateCache should also show the full record. The only mystery is how you could end up with only partial fields in the return value of dsResponse.getData(), especially when that disagrees with the RPC tab, which is showing the exact same object.

                  We're doing a sanity check of current behavior, but, you're five months behind on patches and you're on 9.0 with a 9.1 available - updating would probably fix your issue.

                  Comment


                    #10
                    We've found and fixed the problem - it was a SmartGWT-specific issue (not SmartClient) created by the GWT integration layer.

                    Tomorrow's builds from 3.1 onward will have the fix.

                    Comment


                      #11
                      Thanks. It is working now.

                      Comment

                      Working...
                      X