Announcement

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

    UpdateCaches on TreeGrid with keepParentsOnFilter

    Hi Community,
    I'm using SmartGWT LGPL 4.1. I'm having some issues trying to call updateCaches() on a filtered databound TreeGrid set up with keepParentsOnFilter flag on.

    I've created a minimal showcase to reproduce the problem. The TreeGrid data is bound to a custom datasource that, for simplicity, returns the whole dataset in response to the first fetch operation; the real datasource is much more complex and calls server endpoints to retrieve and update the data.

    I now decide to filter by "Name" column putting the filter "ch" so that both "Chuck" and "Charles" leaf nodes pass the filter. The parents are kept too thanks to the keepParentsOnFilter flag.

    Finally, I have some external component that updates data server side. The datasource is informed of these changes and updates the TreeGrid through an updateCaches() call. I've mocked this behaviour with the Update button in my showcase: I'm updating the age information of nodes "Elvis" and "Chuck".

    When clicking the button, the observed behaviour is that both updated nodes are removed from the filtered tree. Only removing the filter the updated nodes reappear.
    What I've managed to understand by debugging is that, when the ResultTree is informed of the updated nodes, it does not keep into consideration the keepParentsOnFilter flag; as a consequence, when the "Elvis" folder is analyzed it does not pass the filter and is removed from the filtered tree along with its child "Chuck".

    Is there something I am setting up or doing wrong? Thanks for any advice.

    TreeDataSource.java
    Code:
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import com.smartgwt.client.data.DSRequest;
    import com.smartgwt.client.data.DSResponse;
    import com.smartgwt.client.data.DataSource;
    import com.smartgwt.client.data.fields.DataSourceIntegerField;
    import com.smartgwt.client.data.fields.DataSourceTextField;
    import com.smartgwt.client.types.CriteriaPolicy;
    import com.smartgwt.client.types.DSOperationType;
    import com.smartgwt.client.types.DSProtocol;
    import com.smartgwt.client.widgets.tree.TreeNode;
    
    public class TreeDataSource extends DataSource {
    
        private final Map<String, TreeNode> dataset = new HashMap<String, TreeNode>();
    
        public TreeDataSource() {
            setClientOnly(true);
            setDataProtocol(DSProtocol.CLIENTCUSTOM);
            setCriteriaPolicy(CriteriaPolicy.DROPONCHANGE);
    
            DataSourceTextField idField = new DataSourceTextField("id");
            idField.setPrimaryKey(true);
            idField.setHidden(true);
    
            DataSourceTextField parentIdField = new DataSourceTextField("parentId");
            parentIdField.setForeignKey("id");
            parentIdField.setHidden(true);
    
            DataSourceTextField nameField = new DataSourceTextField("name");
            nameField.setCanFilter(true);
    
            DataSourceIntegerField valueField = new DataSourceIntegerField("age");
            valueField.setCanFilter(true);
    
            setFields(idField, parentIdField, nameField, valueField);
    
            populateDataset();
        }
    
        protected void populateDataset() {
            TreeNode johnNode = new TreeNode();
            johnNode.setAttribute("id", "1");
            johnNode.setAttribute("parentId", (String) null);
            johnNode.setAttribute("name", "John");
            johnNode.setAttribute("age", 24);
            johnNode.setIsFolder(true);
            dataset.put("1", johnNode);
    
            TreeNode elvisNode = new TreeNode();
            elvisNode.setAttribute("id", "2");
            elvisNode.setAttribute("parentId", "1");
            elvisNode.setAttribute("name", "Elvis");
            elvisNode.setAttribute("age", 32);
            elvisNode.setIsFolder(true);
            dataset.put("2", elvisNode);
    
            TreeNode chuckNode = new TreeNode();
            chuckNode.setAttribute("id", "3");
            chuckNode.setAttribute("parentId", "2");
            chuckNode.setAttribute("name", "Chuck");
            chuckNode.setAttribute("age", 28);
            chuckNode.setIsFolder(false);
            dataset.put("3", chuckNode);
    
            TreeNode bartNode = new TreeNode();
            bartNode.setAttribute("id", "4");
            bartNode.setAttribute("parentId", "1");
            bartNode.setAttribute("name", "Bart");
            bartNode.setAttribute("age", 55);
            bartNode.setIsFolder(true);
            dataset.put("4", bartNode);
    
            TreeNode charlesNode = new TreeNode();
            charlesNode.setAttribute("id", "5");
            charlesNode.setAttribute("parentId", "4");
            charlesNode.setAttribute("name", "Charles");
            charlesNode.setAttribute("age", 44);
            charlesNode.setIsFolder(false);
            dataset.put("5", charlesNode);
    
            TreeNode ericNode = new TreeNode();
            ericNode.setAttribute("id", "6");
            ericNode.setAttribute("parentId", "1");
            ericNode.setAttribute("name", "Eric");
            ericNode.setAttribute("age", 25);
            ericNode.setIsFolder(true);
            dataset.put("6", ericNode);
    
            TreeNode fordNode = new TreeNode();
            fordNode.setAttribute("id", "7");
            fordNode.setAttribute("parentId", "6");
            fordNode.setAttribute("name", "Ford");
            fordNode.setAttribute("age", 18);
            fordNode.setIsFolder(false);
            dataset.put("7", fordNode);
        }
    
        @Override
        protected Object transformRequest(final DSRequest dsRequest) {
            if (dsRequest.getOperationType() == DSOperationType.FETCH) {
                executeFetch(dsRequest);
            } else {
                throw new RuntimeException("Not supported operation type " + dsRequest.getOperationType().toString());
            }
            return dsRequest.getData();
        }
    
        protected void executeFetch(final DSRequest dsRequest) {
            String requestId = dsRequest.getRequestId();
            Collection<TreeNode> nodes = dataset.values();
            DSResponse response = new DSResponse();
            response.setData(nodes.toArray(new TreeNode[nodes.size()]));
            processResponse(requestId, response);
        }
    
        public void externalUpdate() {
            TreeNode elvisNode = dataset.get("2");
            elvisNode.setAttribute("age", 33);
    
            TreeNode chuckNode = dataset.get("3");
            chuckNode.setAttribute("age", 29);
    
            List<TreeNode> nodes = new ArrayList<TreeNode>(2);
            nodes.add(elvisNode);
            nodes.add(chuckNode);
    
            DSResponse response = new DSResponse();
            response.setData(nodes.toArray(new TreeNode[nodes.size()]));
            response.setOperationType(DSOperationType.UPDATE);
            updateCaches(response);
        }
    
    }
    TreeGridExample.java
    Code:
    import com.google.gwt.core.client.EntryPoint;
    import com.smartgwt.client.types.Alignment;
    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.layout.VLayout;
    import com.smartgwt.client.widgets.tree.TreeGrid;
    import com.smartgwt.client.widgets.tree.TreeGridField;
    
    public class TreeGridExample implements EntryPoint {
    
        @Override
        public void onModuleLoad() {
            final TreeDataSource treeDataSource = new TreeDataSource();
    
            TreeGrid personTree = new TreeGrid();
            personTree.setWidth(300);
            personTree.setHeight(400);
            personTree.setDataSource(treeDataSource);
            personTree.setAutoFetchData(true);
            personTree.setShowFilterEditor(true);
            personTree.setKeepParentsOnFilter(true);
            personTree.setFilterOnKeypress(true);
            TreeGridField nameField = new TreeGridField("name", "Name", 200);
            TreeGridField ageField = new TreeGridField("age", "Age", 100);
            personTree.setFields(nameField, ageField);
    
            IButton button = new IButton("Update");
            button.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    treeDataSource.externalUpdate();
                }
            });
            button.setLayoutAlign(Alignment.CENTER);
    
            VLayout mainView = new VLayout(10);
            mainView.setHeight100();
            mainView.setWidth100();
            mainView.addMember(personTree);
            mainView.addMember(button);
    
            mainView.draw();
        }
    }

    #2
    If you haven't already tested with the latest patched 4.1 (see smartclient.com/builds), please do so, and remember to always post your full version.

    Also, have you tried this with 6.0? You should not be doing new development on 4.1, it's several releases behind.

    Comment


      #3
      Hello again and thanks for your suggestions.

      I'm actually writing on behalf of my project team. We are using SmartGWT in our application from a long time now; that is why we are still using version 4.1. Migration to a new version is in our roadmap but it will take a good amount of time.

      Going to the point. The real TreeGrid and its datasource is part of a much more complex SmartGWT module, that is why is impossible for me to post here the real code.
      I've created the minimal working showcase that is causing the exact same problem we are experiencing. Using this showcase I was also able to test it with different versions of SmartGWT (otherwise it would have been impossible to test v6.0 in our real application so easily).

      Unfortunately, I've downloaded and tested the latest version of both 4.1 and 6.0 (nightly build of 2017-02-23) and I'm experiencing the same behaviour.

      Thanks again for any advice.

      Comment


        #4
        First problem with your code: you take the actual DataSource Record and change it, then pass that to updateCaches(). This means that the ResultTree receives a notification of an un update, but finds that the existing cached node is already updated.

        You should fix this, but it probably isn't the problem here - we'll take a look.

        Comment


          #5
          Yes this is actually a difference from the showcase and the real application I didn't think about.

          However, I can confirm that, in the real application, the TreeNodes passed to the updateCaches() are newly created.

          Following your suggestion I've also updated the externalUpdate() method in the showcase with the following snippet to be more compliant.

          Code:
          public void externalUpdate() {
               TreeNode elvisNode = dataset.get("2");
               TreeNode copiedElvisNode = copyNode(elvisNode);
               copiedElvisNode.setAttribute("age", 33);
               dataset.put("2", copiedElvisNode);
          
               TreeNode chuckNode = dataset.get("3");
               TreeNode copiedChuckNode = copyNode(chuckNode);
               copiedChuckNode.setAttribute("age", 29);
               dataset.put("3", copiedChuckNode);
          
               List<TreeNode> nodes = new ArrayList<TreeNode>(2);
               nodes.add(copiedElvisNode);
               nodes.add(copiedChuckNode);
          
               DSResponse response = new DSResponse();
               response.setData(nodes.toArray(new TreeNode[nodes.size()]));
               response.setOperationType(DSOperationType.UPDATE);
               updateCaches(response);
          }
              
          private TreeNode copyNode(TreeNode node){
               TreeNode copy = new TreeNode();
               copy.setAttribute("id", node.getAttribute("id"));
               copy.setAttribute("parentId", node.getAttribute("parentId"));
               copy.setAttribute("name", node.getAttribute("name"));
               copy.setAttribute("age", node.getAttributeAsInt("age"));
               copy.setIsFolder(node.getAttributeAsBoolean("isFolder"));
               return copy;
          }

          Comment


            #6
            We've ported a change back to SGWT 4.1p to ensure that if TreeGrid.keepParentsOnFilter is true, then updates to nodes that are visible because they have descendants matching the filter are not dropped. However, you should really update to a newer version, such as the current release, SGWT 6.0p. Significant Framework changes that may better address all related corner cases won't be ported back to such an old release.

            The fix should be in the nightly builds dated 2017-03-09 and beyond.

            Comment

            Working...
            X