Announcement

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

    ListGrid.filterData() callback question

    I'm using SmartClient_v121p_2020-04-14_PowerEdition. I have a ListGrid backed by a "clientOnly" DataSource. I also have a FilterBuilder component that is used to filter the ListGrid. After the user enters the desired criteria and clicks my "Apply Filter" button, I filter the ListGrid as such:

    Code:
    const criteria = filterBuilder.getCriteria();
    
    listGrid.filterData(criteria);
    This works as designed and my grid is filtered appropriately. Once the data is filtered, I need to use the filtered data to perform another function in the application. I need to make sure the filterData operation is complete before actually trying to retrieve the filtered data to use in the other function. According to the documentation, you can add in a callback:

    callback (optional) - type: DSCallback
    callback to invoke when a fetch is complete. Fires only if server contact was required; see fetchData() for details
    So, I changed the code to this:

    Code:
    listGrid.filterData(criteria, function(dsResponse, data, dsRequest) {
        // DO SOMETHING HERE
    });
    I see that it says that the callback will only fire if server contact was required, but I am not sure what is actually happening in this case as the callback is not being fired. I thought that invoking the filterData function on the ListGrid would use the underlying DataSource to filter the data, which would count as "server contact" regardless of whether or not it's a clientOnly DataSource. But, I am sure I'm misunderstanding how this is working. I also saw this in the fetchData() documentation:

    Changes to criteria may or may not result in a DSRequest to the server due to client-side filtering. You can call willFetchData(criteria) to determine if new criteria will result in a server fetch.

    If you need to force data to be re-fetched, you can call invalidateCache() and new data will automatically be fetched from the server using the current criteria and sort direction. NOTE: when using invalidateCache() there is no need to also call fetchData() and in fact this could produce unexpected results.
    I don't know that calling invalidateCache() is really what I should be doing. So my question is, how do I ensure that the filterData operation is complete since I don't think I can rely on the callback? Thanks in advance for your help.

    #2
    Hi danaconway,

    are you saying that the callback does not fire even though there was a server contact? Did you check the Developer Console RPC Tab for the contact, the normal Console for error messages?
    If that's all OK, I'd say the code should look like this:
    Code:
    const criteria = filterBuilder.getCriteria();
    if (!listGrid.willFetchData(criteria)) {
       myMethod();
    } else {
       listGrid.filterData(criteria, function(dsResponse, data, dsRequest) {
          myMethod();
       });
    }
    If this is not working I assume you'll have to create a testcase, which should be easy given that this is already a clientOnly DataSource.

    I don't think you'll need or want invalidateCache().

    Best regards
    Blama
    Last edited by Blama; 8 Nov 2021, 13:35.

    Comment


      #3
      Hi Blama,

      Thank you for your response. I guess what I'm saying is that since I'm using a clientOnly DataSource, there is technically no server contact. This DataSource gets updated by data coming over a websocket. When I click the "Apply Filter" button, the filter operation is only working on the cached client data. The RPC tab shows nothing nor do I get any errors. However, the filterData function is still asynchronous and I need a way to know when the operation is complete. I thought I'd be able to use the callback, but it's just not firing.

      Dana

      Comment


        #4
        Hi danaconway,

        if the way I see it is correct, it is like this: "No entry in RPC Manager Tab of Developer Console" = "No CallBack executed".
        And if all data is local, you'll only get the 1st fetch in the ListGrid and then everything else is synchronous. Therefore you need willFetchData().

        Best regards
        Blama

        Comment


          #5
          Alternatively to willFetchData() this, try this in your ListGrid properties:
          Code:
          dataProperties: {
             useClientFiltering: false
          }
          Perhaps this way you enforce "server" contact for every filter operation.

          Best regards
          Blama

          Comment


            #6
            Hi Blama ,

            I have tried your suggestions, but have not had any luck. I need a way to know when the filterData operation is complete. I have not had any trouble with any other methods that provide a callback optional parameter. For instance, I have used a callback successfully when calling addData or updateData on my DataSource, e.g.

            Code:
            dataSource.addData(newData, filterCallback);
            I was expecting to be able to do the same on the ListGrid filterData method, but it just doesn't seem to function in the same manner.

            One thing that is not completely clear is that when I want to add/update/delete the data in my grid, I operate on the underlying DataSource. However, from the examples, it seems that you operate on the ListGrid itself for filtering purposes and not the DataSource. Or, am I wrong and should I be operating on the DataSource for filtering purposes in this case?

            Comment


              #7
              It seems that I could work around the issue by doing this:

              Code:
              const criteria = filterBuilder.getCriteria();
              
              dataSource.filterData(criteria, function(dsResponse, data, dsRequest) {
              
                 listGrid.setData(data);
              
                 myFilterFunction(data);
              });
              However, the thing that I don't like about this approach is that when new data arrives, the grid filter doesn't automatically get applied, so I would have to manually do this with each update.

              Comment


                #8
                Hi @danaconway,
                I think that one of the two ways should work and is the correct approach. But that’s for Isomorphic to confirm.
                If you can’t get the handler called without your workaround I think that’s an issue.
                Can you show a test case? Should be easy as it is already a clientOnly DataSource.

                Best regards
                Blama

                Comment


                  #9
                  Just to make it official - Blama's post #2 contains correct code for taking action once filtering completes. This is also the approach that is documented.

                  You don't want to set useClientFiltering:false because client filtering is a huge performance and responsiveness boost.

                  You don't want to call setData() after filterData() because, as you've discovered, this breaks automatic filtering (including breaking the filterEditor, if used).

                  So again the right approach is just to do what's doc'd, and post #2 here shows correct code for this.

                  Comment


                    #10
                    I do appreciate the responses, but the code in post #2 from Blama does not work in my test case. I am posting the code below. Perhaps it will give some insight into what I am doing incorrectly.

                    Code:
                    const testData = [
                     {"id":55,"desc":" ","r":5621,"m":13,"p":22,"time":"02:09:35"},
                     {"id":2,"desc":"TUV","r":2790,"m":1,"p":1111,"time":"23:50:06"},
                     {"id":45,"desc":" ","r":17819,"m":1,"p":1144,"time":"02:49:18"},
                     {"id":1,"desc":" ","r":1000,"m":1,"p":909,"time":"22:39:15"},
                     {"id":49,"desc":" ","r":4129,"m":1,"p":1052,"time":"02:08:34"},
                     {"id":24,"desc":" ","r":6168,"m":1,"p":1384,"time":"02:48:26"},
                     {"id":22,"desc":" ","r":11764,"m":1,"p":1045,"time":"02:49:20"},
                     {"id":27,"desc":" ","r":2199,"m":1,"p":680,"time":"02:49:20"},
                     {"id":21,"desc":" ","r":12129,"m":1,"p":376,"time":"02:48:25"},
                     {"id":19,"desc":" ","r":4917,"m":1,"p":788,"time":"02:49:21"},
                     {"id":53,"desc":" ","r":16384,"m":1,"p":697,"time":"02:48:54"},
                     {"id":51,"desc":" ","r":7911,"m":13,"p":39,"time":"01:44:11"},
                     {"id":44,"desc":" ","r":18245,"m":1,"p":639,"time":"02:33:12"},
                     {"id":52,"desc":" ","r":6090,"m":1,"p":575,"time":"01:40:03"},
                     {"id":29,"desc":" ","r":2402,"m":1,"p":904,"time":"02:09:53"},
                     {"id":32,"desc":" ","r":14600,"m":1,"p":1257,"time":"02:17:13"},
                     {"id":25,"desc":" ","r":10776,"m":13,"p":10,"time":"02:49:19"},
                     {"id":11,"desc":"XYZ","r":2880,"m":1,"p":1144,"time":"23:58:54"},
                     {"id":57,"desc":" ","r":5260,"m":13,"p":21,"time":"02:15:24"},
                     {"id":35,"desc":" ","r":1589,"m":1,"p":655,"time":"02:49:20"},
                     {"id":42,"desc":" ","r":3286,"m":13,"p":33,"time":"02:22:48"},
                     {"id":50,"desc":" ","r":10322,"m":1,"p":1352,"time":"01:38:42"},
                     {"id":50,"desc":" ","r":10322,"m":1,"p":1352,"time":"01:38:42"},
                     {"id":30,"desc":" ","r":15278,"m":1,"p":1311,"time":"02:49:20"},
                     {"id":28,"desc":" ","r":2004,"m":1,"p":441,"time":"02:15:14"},
                     {"id":23,"desc":" ","r":5305,"m":13,"p":84,"time":"02:03:38"},
                     {"id":31,"desc":" ","r":851,"m":1,"p":715,"time":"02:49:19"},
                     {"id":26,"desc":" ","r":1639,"m":1,"p":210,"time":"02:49:20"},
                     {"id":54,"desc":" ","r":16384,"m":1,"p":697,"time":"02:01:56"},
                     {"id":47,"desc":" ","r":16317,"m":13,"p":35,"time":"02:41:54"},
                     {"id":9,"desc":" ","r":2800,"m":1,"p":909,"time":"23:59:57"},
                     {"id":43,"desc":" ","r":14695,"m":1,"p":439,"time":"02:49:20"},
                     {"id":4,"desc":"FGH","r":2840,"m":1,"p":1111,"time":"23:29:04"},
                     {"id":37,"desc":" ","r":4349,"m":1,"p":1316,"time":"02:49:19"},
                     {"id":3,"desc":"XYZ","r":2810,"m":1,"p":1111,"time":"22:51:52"},
                     {"id":7,"desc":" ","r":2860,"m":1,"p":766,"time":"22:51:31"},
                     {"id":15,"desc":"PQR","r":2830,"m":1,"p":766,"time":"23:29:04"},
                     {"id":41,"desc":" ","r":15489,"m":1,"p":1049,"time":"02:48:55"},
                     {"id":18,"desc":" ","r":2830,"m":1,"p":766,"time":"12:08:42"},
                     {"id":13,"desc":" ","r":2810,"m":1,"p":1111,"time":"08:55:33"},
                     {"id":48,"desc":" ","r":16384,"m":1,"p":697,"time":"01:35:09"},
                     {"id":5,"desc":"XYZ","r":2870,"m":1,"p":766,"time":"22:52:10"},
                     {"id":6,"desc":"PQR","r":2830,"m":1,"p":766,"time":"23:59:57"},
                     {"id":20,"desc":" ","r":10926,"m":1,"p":810,"time":"02:49:20"},
                     {"id":16,"desc":" ","r":2804,"m":12,"p":35,"time":"11:10:04"},
                     {"id":56,"desc":" ","r":5621,"m":6,"p":134,"time":"02:12:27"},
                     {"id":12,"desc":" ","r":2810,"m":1,"p":1111,"time":"22:44:15"},
                     {"id":36,"desc":" ","r":16574,"m":1,"p":295,"time":"02:49:06"},
                     {"id":33,"desc":" ","r":4455,"m":1,"p":702,"time":"02:45:17"}
                    ];
                    
                    // Define variables to be used later;
                    let gridFilter, mapFilterForm, myListGrid;
                    
                    // Define the client only data source for the grid
                    let myListGridDS = isc.DataSource.create({
                        ID: "myListGridDS",
                        fields: [
                            {name: "id", type: "integer", primaryKey: true},
                            {name: "desc", type: "text"},
                            {name: "r", type: "float"},
                            {name: "m", type: "integer"},
                            {name: "p", type: "float"},
                            {name: "time", type: "datetime", timeUnitOptions: ["hour", "minute", "second"]}
                        ],
                        dataFormat: "json",
                        clientOnly: true
                    });
                    
                    
                    // Create the layout to include a filter builder and a list grid
                    isc.VLayout.create({
                        width: "100%",
                        height: "100%",
                        padding: 5,
                        members: [
                            isc.VLayout.create({
                                width: "100%", height: "15%",
                                padding: 0,
                                members: [
                                    // Filter for grid
                                    gridFilter = isc.FilterBuilder.create({
                                        ID:"gridFilter",
                                        dataSource: "myListGridDS",
                                        width: "98%",
                                        height: "10%",
                                        fieldPickerWidth: 120,
                                        operatorPickerWidth: 120,
                                        valueItemWidth: 100,
                                        padding: 5
                                    }),
                                    isc.HLayout.create({
                                        width: "100%",
                                        height: 35,
                                        members: [
                                            mapFilterForm = isc.DynamicForm.create({
                                                numCols: 4,
                                                fields: [
                                                    {name: "applyToMap", title: "Apply to map", type: "checkbox", redrawOnChange: true, width: 100, defaultValue: false},
                                                    {name: "hideUnmatched", title: "Hide unmatched", type: "checkbox", width: 100, defaultValue: false,
                                                        showIf: "form.getValue('applyToMap') === true "}
                                                ]
                                            }),
                                            isc.LayoutSpacer.create({
                                                width: "30%"
                                            }),
                                            isc.IButton.create({
                                                ID: "gridFilterButton",
                                                title: "Apply Filter",
                                                height: 30,
                                                width: 120,
                                                click: function(){
                    
                                                    const criteria = gridFilter.getCriteria();
                    
                                                    // TODO: This works in the sense of the list grid is guaranteed to be filtered
                                                    // each time. However, the callback doesn't get fired so I don't know when to
                                                    // call checkMapFilter()
                                                    //
                                                    //myListGrid.filterData(criteria, function(dsResponse, data, dsRequest) {
                                                    // checkMapFilter();
                                                    //});
                    
                                                    // TODO: This doesn't seem to work. Technically, there is no server call since
                                                    // we are using client only data, so we fall into the first condition and filterData()
                                                    // never gets called.
                                                    if( !myListGrid.willFetchData(criteria) ) {
                                                        checkMapFilter();
                                                    } else {
                                                        myListGrid.filterData(criteria, function(dsResponse, data, dsRequest) {
                                                            checkMapFilter();
                                                        });
                                                    }
                                                }
                                            }),
                                            isc.LayoutSpacer.create({
                                                width: 5
                                            }),
                                            isc.IButton.create({
                                                ID: "clearFilterButton",
                                                title: "Clear Filter",
                                                height: 30,
                                                width: 120,
                                                click: function(){
                                                    myListGrid.clearCriteria();
                    
                                                    // Notify map controller to clear filtering
                                                    // clearMapFilters();
                                                }
                                            })
                                        ]
                                    })
                                ]
                            }),
                            myListGrid = isc.ListGrid.create({
                                ID: "myListGrid",
                                width: "100%", height: "*",
                                alternateRecordStyles: true,
                                dataSource: "myListGridDS",
                                autoFetchData: true,
                                emptyMessage: "No data available",
                                canFreezeFields: true,
                                selectionType: "multiple",
                                cellHeight: 30,
                                wrapCells: true,
                                primaryKey: "id",
                                fields: [
                                    {name: "id", title: "ID", width: 70, align: "center", frozen: true, canHide: false},
                                    {name: "desc", title: "Name", align: "center"},
                                    {name: "r", title: "R", align: "center"},
                                    {name: "p", title: "P", align: "center"},
                                    {name: "m", title: "M", align: "center"},
                                    {name: "time", title: "Time", align: "center"}
                                ],
                                sortField: "id",
                                sortDirection: "ascending"
                            })
                        ]
                    });
                    
                    // Set the initial cache data
                    myListGridDS.setCacheData(testData);
                    
                    
                    // This should be called AFTER myListGrid.filterData() is complete
                    function checkMapFilter() {
                        // Check to see if we need to apply this filtering to the map
                        let applyToMap = mapFilterForm.getField("applyToMap").getValue();
                        if( applyToMap ) {
                            // Check to see if the user wants to hide the unmatched items
                            let hideOthers = mapFilterForm.getField("hideUnmatched").getValue();
                    
                            // Notify map controller that we need to filter the map and send applyToMap and hideOthers values
                            // setMapFilters(applyToMap, hideOthers);
                        } else {
                            // If apply to map gets unchecked, make sure we clear any previous filters
                            // clearMapFilters();
                        }
                    }

                    Comment


                      #11
                      Thanks for the test case, but you'll need to clarify precisely what you're doing, what you're seeing and what you expect.

                      Specifically, you have a DS field declared of type "datetime" in which you've provided String values, not Date instances, so that won't work at all. So what criteria are you specifically trying?

                      Did you check your Developer Console for warnings and JS errors?

                      How are you checking whether your callback is called? Please check by logging a warning rather than, e.g., just seeing if the final result is as expected - which may not occur due to other bugs in your code.

                      Comment


                        #12
                        Hi danaconway ,

                        sorry, my code from #2 has a obvious error. It should be like this:
                        Code:
                        const criteria = filterBuilder.getCriteria();
                        if (!listGrid.willFetchData(criteria)) {
                           listGrid.filterData(criteria); // Added line, we know it is synchronous because of the "if" above
                           myMethod();
                        } else {
                           listGrid.filterData(criteria, function(dsResponse, data, dsRequest) {
                              myMethod();
                           });
                        }
                        Best regards
                        Blama

                        Comment


                          #13
                          Thanks Blama, humbly helpful as ever (we missed it too!).

                          Comment

                          Working...
                          X