Announcement

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

    ListGrid.removeRecordClick does not make record disappear

    Hi,

    I am using v11.1p_2019-01-03/LGPL.

    I have a databound ListGrid with canRemoveRecords = true, deferRemoval = false.
    When removing records using removeIcon (removeRecordClick method) record is not remove from ListGrid however it is removed from DataSource - correct DSRequest is sent and very simple DSRespone { data: { primaryKeyField: 111}, status: 0, errors: null } is being sent back from my server.
    If ListGrid has animateRemoveRecord = true, the record is folded but then suddenly pops back, if animation is turned off, nothing is happening visually.
    The record is still present in ListGrid.data.

    If I am using ListGrid's removeData or removeSelectedData exactly the same DSRequest and DSResponse are being sent/received and everything works as expected.

    Best regards,
    Janusz

    #2
    We would suggest turning on the ResultSet log category to see what's going on. Something subtle is probably wrong with your response.

    If that doesn't lead to a solution, we'll need a test case that shows the problem, as there are Showcase samples where this feature is operating as expected.

    Comment


      #3
      There is only small difference in logs between clicking "remove icon" (top) and calling "removeSelectedData" (bottom) - for "remove icon" there is an extra getRange(0,3) before the dataSource data change was fired.
      Before removing each item there were 4 items in ResultSet.

      Code:
      *16:16:49.427:TMR3:DEBUG:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):getRange(0, 3) satisfied from cache
      *16:16:49.766:XRP6:DEBUG:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):dataSource data changed firing
      *16:16:49.770:XRP6:INFO:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):updating cache in place after operationType: remove, allMatchingRowsCached true
      *16:16:49.772:XRP6:INFO:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):Updating cache: operationType 'remove' submitted by 'adminPromotionsGrid',1 rows update data:
      [
      {promotion_id: 4237, active: undef, start_datetime: undef, end_datetime: undef, flag_id: undef, pricelist_1_id: undef, pricelist_2_id: undef, pricelist_3_id: undef, discount_2: undef, discount_3: undef, promotion_name: undef, promotion_type: undef, usergroup_id: undef, discount_1: undef, promotion_title: undef}
      ]
      *16:16:49.789:TMR7:DEBUG:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):getRange(0, 3) satisfied from cache
      Code:
      *16:17:31.313:XRP0:DEBUG:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):dataSource data changed firing
      *16:17:31.316:XRP0:INFO:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):updating cache in place after operationType: remove, allMatchingRowsCached true
      *16:17:31.316:XRP0:INFO:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):Updating cache: operationType 'remove' submitted by 'adminPromotionsGrid',1 rows update data:
      [
      {promotion_id: 4238, active: undef, start_datetime: undef, end_datetime: undef, flag_id: undef, pricelist_1_id: undef, pricelist_2_id: undef, pricelist_3_id: undef, discount_2: undef, discount_3: undef, promotion_name: undef, promotion_type: undef, usergroup_id: undef, discount_1: undef, promotion_title: undef}
      ]
      *16:17:31.329:TMR1:DEBUG:ResultSet:isc_ResultSet_1 (dataSource: promotions, created by: adminPromotionsGrid):getRange(0, 3) satisfied from cache
      Just for completeness here are sent dsRequest and dsResponse for both operations.

      Code:
      {
          "dataSource":"promotions", 
          "operationType":"remove", 
          "textMatchStyle":"exact", 
          "componentId":"adminPromotionsGrid", 
          "data":{
              "active":false, 
              "promotion_name":"test", 
              "promotion_type":"pricelist", 
              "start_datetime":null, 
              "end_datetime":null, 
              "flag_id":null, 
              "pricelist_1_id":null, 
              "pricelist_2_id":null, 
              "pricelist_3_id":null, 
              "discount_2":null, 
              "discount_3":null, 
              "usergroup_id":null, 
              "discount_1":null, 
              "promotion_title":null, 
              "_selection_93":true, 
              "_primaryKey_promotion_id":4237
          }, 
          "oldValues":null
      }
      
      {"response":{"status":0,"data":{"promotion_id":4237},"errors":null}
      Code:
      {
          "dataSource":"promotions", 
          "operationType":"remove", 
          "textMatchStyle":"exact", 
          "componentId":"adminPromotionsGrid", 
          "data":{
              "_primaryKey_promotion_id":4238
          }, 
          "oldValues":null
      }
      
      {"response":{"status":0,"data":{"promotion_id":4238},"errors":null}
      Best regards,
      Janusz

      Comment


        #4
        Ok, as we’ve already covered, since this is all working as expected in samples, we need a way to reproduce the problem.

        Comment


          #5
          After few hours and removing code part by part I have narrowed it to a fragment of custom transformRequest that is removing from dsRequest.data fields that should never be changed on the client. In this specific case it is removing numeric primary key - primary keys of edited record are sent to my backend in _primaryKey_xxx fields not as regular data.
          Despite replying with proper dsResponse containing object with only primary key of deleted record, SmartClient somehow can't match this reply to a record in ResultSet and remove this deleted record.

          Here is a fragment of code:
          Code:
          transformRequest: function (dsRequest) {
                if(dsRequest.operationType != 'fetch') {
              var allFields = this.getFields(), i;
              for(i in allFields) {
               var field = allFields[i];
                if(field.canSave === false) {
                  delete dsRequest.data[field.name];
                }
              }
                }
                return this.Super("transformRequest", dsRequest, arguments);
              }

          Comment


            #6
            Good. This code is incorrect and should be removed. Please refer to the QuickStart Guide and ResultSet documentation, which clearly explains the expected data to be returned.

            Comment


              #7
              I have removed this code altogether as I have implemented removing unnecessary fields on server quite some time ago so this another line of defense is not needed anymore, however custom transformRequest is used according to https://www.smartclient.com/smartcli...ansformRequest that states that it is a place to modify dsRequest.data - in my scenario to remove some fields.

              Sorry, but I do not see anything in ResultSet doc or in QuickStart that should be related to this part of code.
              I would like very much learn what you have meant to avoid anything similar in future.

              My guess at the moment is that calling removeRecord as a result of completeRemoveRecordClick and removeRecordClick calls listGrid.removeData with actual record object which is later passed to DSRequest as dsRequest.data.
              My code removing primaryKey from dsRequest.data is removing it from ListGrid.data record breaking a record in ResultSet. Without primaryKey it can't be removed when server replies.
              At some point in remove... functions chain record object should be cloned to delivery a copy to dsRequest.data.

              Comment


                #8
                If your server is replying with data that lacks the primaryKey or lacks other fields, that breaks the basic assumptions of the data binding system as described in the QuickStart.

                As far as removing other fields on the way to the server, that’s generally fine, but if you are blindly modifying dsRequest.data for a request you didn’t submit, something like this could happen. It’s trivial to check whether this is the problem, so let us know.

                Comment


                  #9
                  Sorry, but you misunderstood my post. Now I am sure that this is a bug in SmartClient as I was able to track down at fix it.

                  My server is replying with proper dsRequest - for remove operations it contains data with primaryKey only. Everything works fine when it comes to ListGrid.removeSelectedRecords, DataSource.removeData etc.
                  The only problem is with ListGrid.removeRecordClick because of chain of methods leading finally to DataSource.performDSOperation.

                  The difference between removeSelectedData (and others) and removeRecordClick was the the first is calling finally performDSOperation("remove",...) with a copy/clone of records, but and the second is calling performDSOperation("remove",...) with actual record from ListGrid.data that was passed by reference.

                  Take a look at DataSource.removeData:
                  Code:
                  removeData : function (recordKeys, callback, requestProperties) {
                          if (isc.Offline && isc.Offline.isOffline() && this.contactsServer()) {
                              isc.logWarn("Data cannot be saved because you are not online");
                              return;
                          }
                          // if passed a full record, trim it down to primaryKeys.
                          // This will avoid problems where additional properties such as _selected etc
                          // are present on the record
                  
                          var keyFieldNames = this.getPrimaryKeyFieldNames(),
                              recordKeys = isc.applyMask(recordKeys, keyFieldNames);
                          this.performDSOperation("remove", recordKeys, callback, requestProperties);
                      },
                  As you can see it calls isc.applyMask which makes a copy of recordKeys when removing all fields except primary keys.

                  And compare it with a chain of methods firing after clicking on remove icon in ListGrid:
                  1. ListGrid.removeRecordClick
                  2. ListGrid.completeRemoveRecordClick
                  3. ListGrid.removeRecord
                  4. ListGrid.removeData
                  5. DataBoundComponent.removeData
                  6. DataSource.performDSOperation

                  (2) gets a record from ListGrid and that record is passed by reference down to (6). At no point between (2) and (6) you call isc.clone on record so finally RestDataSource.transformRequest gets dsRequest with attribute data that is a reference to a record from ListGrid.data. By modyfing it in transformRequest I am modyfing a live record which should not be done - think about a network failure, server error etc. that will leave me with ListGrid.data modified by transformRequest even if the request failed.

                  The fix is very easy and would consist of changing DataBoundComponent.removeData from
                  Code:
                  removeData : function (recordKeys, callback, requestProperties) {
                      return this._performDSOperation("remove", recordKeys, callback, requestProperties);
                  }
                  to
                  Code:
                  removeData : function (recordKeys, callback, requestProperties) {
                      return this._performDSOperation("remove", isc.clone(recordKeys), callback, requestProperties);
                  }

                  Comment


                    #10
                    No, we didn’t misunderstand, we covered both possibilities - note the part about “blindly modifying dsRequest.data for a request you didn’t submit” - that’s what your code does, and doing a copy in your code (in transformRequest) in the correct fix here.

                    Note that your attempted patch would crash (with trees and other cases).

                    However there are many places in which the framework is written so that even incorrect application code still works, and this is a place where we might harden the code against this particular coding error, in future versions.



                    Comment

                    Working...
                    X