Success! Looks like we've fixed this one. According to our records the fix was applied for EXTGWT-2644 in 3.0.4.
  1. #11
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Thanks for the details, but again: I don't have your code, I can't reproduce your issue, so I can't get details like this without guessing. I understand this is important to you, but you aren't making it easy for me to help you.

  2. #12
    Sencha User
    Join Date
    May 2011
    Location
    Netherlands
    Posts
    45
    Vote Rating
    1
    rcbeuker is on a distinguished road

      1  

    Default Example code

    Example code


    Hello Colin,

    The following example code reproduces the bug. it is a variation on the AsyncTreeGridExample;

    Code:
    package com.arcus.pba.client.shared.widget.drivefile.test;
    
    import com.google.gwt.cell.client.DateCell;
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.core.client.GWT;
    import com.google.gwt.editor.client.Editor;
    import com.google.gwt.event.dom.client.ClickEvent;
    import com.google.gwt.event.dom.client.ClickHandler;
    import com.google.gwt.i18n.client.DateTimeFormat;
    import com.google.gwt.user.client.rpc.AsyncCallback;
    import com.google.gwt.user.client.ui.IsWidget;
    import com.google.gwt.user.client.ui.RootPanel;
    import com.google.gwt.user.client.ui.Widget;
    import com.google.gwt.widget.client.TextButton;
    import com.sencha.gxt.core.client.ValueProvider;
    import com.sencha.gxt.data.client.loader.RpcProxy;
    import com.sencha.gxt.data.shared.ModelKeyProvider;
    import com.sencha.gxt.data.shared.PropertyAccess;
    import com.sencha.gxt.data.shared.TreeStore;
    import com.sencha.gxt.data.shared.loader.ChildTreeStoreBinding;
    import com.sencha.gxt.data.shared.loader.TreeLoader;
    import com.sencha.gxt.widget.core.client.ContentPanel;
    import com.sencha.gxt.widget.core.client.FramedPanel;
    import com.sencha.gxt.widget.core.client.container.BorderLayoutContainer;
    import com.sencha.gxt.widget.core.client.container.BoxLayoutContainer;
    import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
    import com.sencha.gxt.widget.core.client.grid.ColumnModel;
    import com.sencha.gxt.widget.core.client.toolbar.ToolBar;
    import com.sencha.gxt.widget.core.client.treegrid.TreeGrid;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.List;
    
    public class AsyncTreeGridExample implements IsWidget, EntryPoint
    {
        private int mNewId = 0;
        private FileModel mClipboardItem;
        private ClickHandler mButtonSelectHandler;
        private TreeGrid<FileModel> mTree;
        private TreeLoader<FileModel> mLoader;
        private ArrayList mMapA;
        private ArrayList mMapB;
        private ArrayList mMapC;
    
        public interface FileModelProperties extends PropertyAccess<FileModel>
        {
            @Editor.Path("id")
            ModelKeyProvider<FileModel> key();
            ValueProvider<FileModel, String> name();
            ValueProvider<FileModel, String> path();
            ValueProvider<FileModel, Date> lastModified();
            ValueProvider<FileModel, Long> size();
        }
    
        @Override
        public void onModuleLoad()
        {
            RootPanel.get().add(this);
        }
    
        @Override
        public Widget asWidget()
        {
            FileModel lDoc1 = new FileModel("Doc1", "");
            lDoc1.setId(getNewId());
            mMapA = new ArrayList<FileModel>(Arrays.asList(lDoc1));
    
            FileModel lDoc2 = new FileModel("Doc2", "");
            lDoc2.setId(getNewId());
            mMapB = new ArrayList<FileModel>(Arrays.asList(lDoc2));
    
            FileModel lDoc3 = new FileModel("Doc3", "");
            lDoc3.setId(getNewId());
            mMapC = new ArrayList<FileModel>(Arrays.asList(lDoc3));
    
            RpcProxy<FileModel, List<FileModel>> proxy = new RpcProxy<FileModel, List<FileModel>>()
            {
                @Override
                public void load(FileModel loadConfig, AsyncCallback<List<FileModel>> callback)
                {
                    if (loadConfig == null)
                    {
                        FolderModel lMapA = new FolderModel("Map A", "/");
                        lMapA.setId(getNewId());
                        FolderModel lMapB = new FolderModel("Map B", "/");
                        lMapB.setId(getNewId());
                        FolderModel lMapC = new FolderModel("Map C", "/");
                        lMapC.setId(getNewId());
    
                        callback.onSuccess(new ArrayList<FileModel>(Arrays.asList(lMapA, lMapB, lMapC)));
                    }
                    else
                    {
                        if (loadConfig.getName().equals("Map A"))
                        {
                            callback.onSuccess(mMapA);
                        }
    
                        if (loadConfig.getName().equals("Map B"))
                        {
                            callback.onSuccess(mMapB);
                        }
    
                        if (loadConfig.getName().equals("Map C"))
                        {
                            callback.onSuccess(mMapC);
                        }
                    }
                }
            };
    
            mLoader = new TreeLoader<FileModel>(proxy)
            {
                @Override
                public boolean hasChildren(FileModel parent)
                {
                    return parent instanceof FolderModel;
                }
            };
    
            FileModelProperties props = GWT.create(FileModelProperties.class);
    
            TreeStore<FileModel> store = new TreeStore<FileModel>(props.key());
            mLoader.addLoadHandler(new ChildTreeStoreBinding<FileModel>(store));
    
            ColumnConfig<FileModel, String> cc1 = new ColumnConfig<FileModel, String>(props.name(), 100, "Name");
    
            ColumnConfig<FileModel, Date> cc2 = new ColumnConfig<FileModel, Date>(props.lastModified(), 100, "Date");
            cc2.setCell(new DateCell(DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_MEDIUM)));
    
            ColumnConfig<FileModel, Long> cc3 = new ColumnConfig<FileModel, Long>(props.size(), 100, "Size");
    
            List<ColumnConfig<FileModel, ?>> l = new ArrayList<ColumnConfig<FileModel, ?>>();
            l.add(cc1);
            l.add(cc2);
            l.add(cc3);
    
            ColumnModel<FileModel> cm = new ColumnModel<FileModel>(l);
    
            FramedPanel cp = new FramedPanel()
            {
                @Override
                protected void onAfterFirstAttach()
                {
                    super.onAfterFirstAttach();
                    mLoader.load();
                }
            };
    
            cp.setHeadingText("Async TreeGrid");
            cp.setButtonAlign(BoxLayoutContainer.BoxLayoutPack.CENTER);
            cp.setPixelSize(600, 300);
    
            mTree = new TreeGrid<FileModel>(store, cm, cc1);
            mTree.setBorders(true);
            mTree.setTreeLoader(mLoader);
            mTree.getView().setTrackMouseOver(false);
            mTree.getView().setAutoExpandColumn(cc1);
    
            cp.setWidget(mTree);
    
            BorderLayoutContainer lBorderLayoutContainer = new BorderLayoutContainer();
            lBorderLayoutContainer.setCenterWidget(cp);
    
            ToolBar lToolBar = new ToolBar();
            TextButton lDeleteButton = new TextButton("DELETE");
            lDeleteButton.addClickHandler(getButtonClickHandler());
            TextButton lCopyButton = new TextButton("COPY");
            lCopyButton.addClickHandler(getButtonClickHandler());
            TextButton lPasteButton = new TextButton("PASTE");
            lPasteButton.addClickHandler(getButtonClickHandler());
    
            lToolBar.add(lCopyButton);
            lToolBar.add(lPasteButton);
            lToolBar.add(lDeleteButton);
    
            ContentPanel lToolbarPanel = new ContentPanel();
            lToolbarPanel.add(lToolBar);
    
            lBorderLayoutContainer.setNorthWidget(lToolbarPanel);
    
            return lBorderLayoutContainer;
        }
    
        ///////////////////////////
        // Arcus-Solutions hacking
        ///////////////////////////
        private String getNewId()
        {
            return new Integer(mNewId++).toString();
        }
    
        private ClickHandler getButtonClickHandler()
        {
            if (mButtonSelectHandler == null)
            {
                mButtonSelectHandler = new ClickHandler()
                {
                    @Override
                    public void onClick(ClickEvent pEvent)
                    {
                        TextButton lTextButton = (TextButton) pEvent.getSource();
                        fireButtonSelection(lTextButton);
                    }
                };
            }
    
            return mButtonSelectHandler;
        }
    
        private void fireButtonSelection(TextButton pTextButton)
        {
            if (pTextButton.getText().equals("DELETE"))
            {
                FileModel lDeleteItem = mTree.getSelectionModel().getSelectedItem();
    
                if (!(lDeleteItem instanceof FolderModel))
                {
                  mMapA.remove(lDeleteItem);
                    mMapB.remove(lDeleteItem);
                    mMapC.remove(lDeleteItem);
                    mLoader.load();
                }
            }
            else if (pTextButton.getText().equals("COPY"))
            {
                mClipboardItem = mTree.getSelectionModel().getSelectedItem();
            }
            else if (pTextButton.getText().equals("PASTE"))
            {
                FileModel lPasteParentItem = mTree.getSelectionModel().getSelectedItem();
    
                if (lPasteParentItem instanceof FolderModel)
                {
                    if (mClipboardItem != null)
                    {
                        String lNewName = "Copy of " + mClipboardItem.getName();
                        FileModel lCopyDoc = new FileModel(lNewName, "");
                        lCopyDoc.setId(getNewId());
    
                        if (lPasteParentItem.getName().equals("Map A"))
                        {
                            mMapA.add(lCopyDoc);
                        }
    
                        if (lPasteParentItem.getName().equals("Map B"))
                        {
                            mMapB.add(lCopyDoc);
                        }
    
                        if (lPasteParentItem.getName().equals("Map C"))
                        {
                            mMapC.add(lCopyDoc);
                        }
    
                        mLoader.load(lPasteParentItem);
                    }
                }
            }
        }
    }
    Schermafbeelding 2012-12-04 om 2.16.53 PM.png

    Schermafbeelding 2012-12-04 om 2.17.40 PM.png

    Open a map, for example Map A, select Doc1 and choose COPY. Then close Map A (keep it selected) and choose PASTE. You'le see a correct copy of Doc1 (Copy of Doc1) when you open Map A again.

    Now again open a map, for example Map B, select Doc2 and again choose COPY. Now leave Map B open, select Map B and choose PASTE. Nothing happens until you close Map B. You'le see a 'ghost Doc2' outside Map B. When you open Map B again you'le see the content of Map B next to the 'ghost Doc2'

    regards,

    Roland Beuker

  3. #13
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      1  

    Default


    Thanks for the example, I can reproduce the issue with this.

    When running this, I get an assertion failure within ListStore that we added since 3.0.2, trying to alert developers to duplicate IDs. Since this is going off, but not the existing duplicate ID checks in the TreeStore (there since 3.0.0), It seems safe to say that the only corruption issue is inside the TreeGrid/TreeGridView's bookkeeping. This was my main concern, and why I've been wanting to get a working test case before trying to attempt a fix - if we're just hiding the issue, we're making someone else's job harder in the future.

    That said, I want to point out that while you probably *do* want the ChildTreeStoreBinding (which loads just one more layer of child nodes instead of an arbitrary number like SubTreeStoreBinding) when loading data, but when *saving* this might be a bad thing. I say this because the kick to the loader that says 'go get these items' by default collapses the node, requiring the user to expand them again.

    I believe this is the root of the bug - the load operation is rebuilding the node to be collapsed, but never cleans out the now-missing children from the dom. Then, the next time you expand, new items are added, and the old are left in the tree (or in my case, an assertion fails).

    Based on this line of thinking, a workaround seems to make sense: Before loading new items for a parent from the server (or, in this example, some local map), clear out the item's current children. This looks like this, and appears to work:
    Code:
            mLoader.addBeforeLoadHandler(new BeforeLoadHandler<FileModel>() {
              @Override
              public void onBeforeLoad(BeforeLoadEvent<FileModel> event) {
                FileModel parent = event.getLoadConfig();
                if (parent == null) {
                  // loading from the root of the tree, no need to clean up
                  return;
                }
                mTree.getTreeStore().removeChildren(parent);
              }
            });
    The joint isn't correctly getting updated though - again, the TreeGrid is making assumptions that apparently aren't valid: that if a node is expanded, there is no need to reload its children - we already know what the children are!

    So, lets add another line at the tail end of that BeforeLoadHandler, collapsing the parent as it loads. Then, I'm assuming that you want the node to be correctly expanded again when it comes back from the server?

    This gets slightly hairy, and again isn't already supported. The naive approach would be to add another handler, and auto-expand as soon as we have loaded, but this will get us in trouble in (at least) 3 ways:
    * What if an error occured? Also need to cover that use case (LoadExceptionEvent)
    * What if the node hadn't been expanded? Need to leave it collapsed.
    * What if the node is being loaded for the first time, not as the result of saving? Need to leave it collapsed.

    My draft implementation of this:
    Code:
    mLoader.addLoaderHandler(new LoaderHandler<FileModel, List<FileModel>>() {
      private boolean loadingExpandedNode = false;
      @Override
      public void onBeforeLoad(BeforeLoadEvent<FileModel> event) {
        assert loadingExpandedNode == false : "Some node already loading, need to rethink using a single boolean";
        FileModel parent = event.getLoadConfig();
        if (parent == null) {
          // loading from the root of the tree, no need to clean up
          return;
        }
        //check if we are loading something already expanded, the source of the bug
        if (mTree.isExpanded(parent)) {
          mTree.setExpanded(parent, false);
          mTree.getTreeStore().removeChildren(parent);
          loadingExpandedNode = true;
        }
      }
      @Override
      public void onLoad(LoadEvent<FileModel, List<FileModel>> event) {
        if (loadingExpandedNode) {
          //current node should be expanded, restore it
          mTree.setExpanded(event.getLoadConfig(), true);
    
          //reset back to false for next load
          loadingExpandedNode = false;
        }
      }
      @Override
      public void onLoadException(LoadExceptionEvent<FileModel> event) {
        if (loadingExpandedNode) {
          //current node should be expanded, restore it
          mTree.setExpanded(event.getLoadConfig(), true);
    
          //reset back to false for next load
          loadingExpandedNode = false;
        }
      }
    });
    As is documented in this workaround, you may have issues with multiple concurrent loads. In that case, you'll need to track each load (or at least expanding child) separately to ensure that you don't have two runs at once. I've got a basic assert in there, but this may miss an issue or two.

    Hopefully this gets you past this bug. We'll update this thread when we have a fix available in the library itself.

  4. #14
    Sencha User
    Join Date
    May 2011
    Location
    Netherlands
    Posts
    45
    Vote Rating
    1
    rcbeuker is on a distinguished road

      0  

    Default


    Hello Colin,

    Nice to see that the ball starts rolling First off all your fix is working
    But there are some concerns;

    When choosing this fix you should beware of using your mLoader.addLoaderHandler() after the mLoader.addLoadHandler() which connects the loader to the store. Otherwise the sequence is wrong (we need the loading first and then the expand handling). This seems trivial but in a bigger program this is an easy mistake (it happend to me). There should be an onAfterLoad() or onLoadReady() for this purposes.

    Next, this code works very well in the small example but in a bigger tree the collapsing and expanding goes slow ending up with collapsing/expanding animations after each copy, move or edit. This looks stupid...

    When looking in the Explorer Demo;

    http://www.sencha.com/examples/#ExamplePlace:treegridtotreegrid

    T
    his example places new files in open parents, isn't this possible with a data update from the server?

    Regards,

    Roland Beuker

  5. #15
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      1  

    Default


    Good catch with the loader handler order - a better fix than ordering the handlers would be to make the load handler that restores the expand/collapse deferred - use the Scheduler class for this.

    The example doesn't force a full loader call to the server - I tried to allude to that in my previous post, but didn't get far enough. Loaders, and the various *TreeStoreBinding classes are designed to pull in full sets of data, but you really only want to update or add individual nodes. If I were you, I'd send just the node that is added/updated over the wire. In the case of update, just invoke store.update to get the same effect we see in the explorer. To add nodes, you need context - parent, and index. Parent can be just the key for the parent object, and index is probably an integer.

    For the sake of completeness, you almost certainly need a remove method too - just the item's key is required to pull this off, since the client can use this to find the item and remove it from the store.

  6. #16
    Sencha User
    Join Date
    May 2011
    Location
    Netherlands
    Posts
    45
    Vote Rating
    1
    rcbeuker is on a distinguished road

      0  

    Default


    Hello Colin,

    I implemented your suggestion (update item and invoke store.update). This works but also produces the error (with the Ghost items) like described above...

    Regards,

    Roland

  7. #17
    Sencha User
    Join Date
    May 2011
    Location
    Netherlands
    Posts
    45
    Vote Rating
    1
    rcbeuker is on a distinguished road

      0  

    Default


    Quote Originally Posted by rcbeuker View Post
    Hello Colin,

    I implemented your suggestion (update item and invoke store.update). This works but also produces the error (with the Ghost items) like described above...

    Regards,

    Roland
    Hello Colin,

    Any suggestion or workaround when using store.update? This produces the same error as mentioned in this issue...

    Regards,

    Roland

  8. #18
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Do you have a sample of how you did that? Specifically, how you update the single item, and get notification about moved/created/deleted nodes? Just changing part of the sample to use update won't be sufficient.

  9. #19
    Sencha User
    Join Date
    May 2011
    Location
    Netherlands
    Posts
    45
    Vote Rating
    1
    rcbeuker is on a distinguished road

      0  

    Default Update sample with drag/drop

    Update sample with drag/drop


    Hello Colin,

    I updated the sample code with drag/drop functionality which shows the store.update bug;

    Code:
    package com.arcus.pba.client.shared.widget.drivefile.bugtest;
    
    
    import com.google.gwt.cell.client.DateCell;
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.core.client.GWT;
    import com.google.gwt.core.client.Scheduler;
    import com.google.gwt.editor.client.Editor;
    import com.google.gwt.event.dom.client.ClickEvent;
    import com.google.gwt.event.dom.client.ClickHandler;
    import com.google.gwt.i18n.client.DateTimeFormat;
    import com.google.gwt.user.client.rpc.AsyncCallback;
    import com.google.gwt.user.client.ui.IsWidget;
    import com.google.gwt.user.client.ui.RootPanel;
    import com.google.gwt.user.client.ui.Widget;
    import com.google.gwt.widget.client.TextButton;
    import com.sencha.gxt.core.client.ValueProvider;
    import com.sencha.gxt.data.client.loader.RpcProxy;
    import com.sencha.gxt.data.shared.ModelKeyProvider;
    import com.sencha.gxt.data.shared.PropertyAccess;
    import com.sencha.gxt.data.shared.TreeStore;
    import com.sencha.gxt.data.shared.loader.BeforeLoadEvent;
    import com.sencha.gxt.data.shared.loader.ChildTreeStoreBinding;
    import com.sencha.gxt.data.shared.loader.LoadEvent;
    import com.sencha.gxt.data.shared.loader.LoadExceptionEvent;
    import com.sencha.gxt.data.shared.loader.LoaderHandler;
    import com.sencha.gxt.data.shared.loader.TreeLoader;
    import com.sencha.gxt.dnd.core.client.DndDropEvent;
    import com.sencha.gxt.dnd.core.client.TreeGridDragSource;
    import com.sencha.gxt.dnd.core.client.TreeGridDropTarget;
    import com.sencha.gxt.widget.core.client.ContentPanel;
    import com.sencha.gxt.widget.core.client.FramedPanel;
    import com.sencha.gxt.widget.core.client.container.BorderLayoutContainer;
    import com.sencha.gxt.widget.core.client.container.BoxLayoutContainer;
    import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
    import com.sencha.gxt.widget.core.client.grid.ColumnModel;
    import com.sencha.gxt.widget.core.client.toolbar.ToolBar;
    import com.sencha.gxt.widget.core.client.tree.Tree;
    import com.sencha.gxt.widget.core.client.treegrid.TreeGrid;
    
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.List;
    
    
    public class AsyncTreeGridExample implements IsWidget, EntryPoint
    {
        protected MyTreeGridDropTarget mGridDropTarget;
        protected TreeGridDragSource<FileModel> mGridDragSource;
        private int mNewId = 0;
        private FileModel mClipboardItem;
        private ClickHandler mButtonSelectHandler;
        private TreeGrid<FileModel> mTree;
        private TreeLoader<FileModel> mLoader;
        private ArrayList mMapA;
        private ArrayList mMapB;
        private ArrayList mMapC;
        private FileModel mPasteParentItem;
        private List<FileModel> mMovedItemList;
        private FileModel mParentItem;
    
    
        @Override
        public void onModuleLoad()
        {
            RootPanel.get().add(this);
        }
    
    
        @Override
        public Widget asWidget()
        {
            FileModel lDoc1 = new FileModel("Doc1", "");
            lDoc1.setId(getNewId());
            mMapA = new ArrayList<FileModel>(Arrays.asList(lDoc1));
    
    
            FileModel lDoc2 = new FileModel("Doc2", "");
            lDoc2.setId(getNewId());
            mMapB = new ArrayList<FileModel>(Arrays.asList(lDoc2));
    
    
            FileModel lDoc3 = new FileModel("Doc3", "");
            lDoc3.setId(getNewId());
            mMapC = new ArrayList<FileModel>(Arrays.asList(lDoc3));
    
    
            RpcProxy<FileModel, List<FileModel>> proxy = new RpcProxy<FileModel, List<FileModel>>()
            {
                @Override
                public void load(FileModel loadConfig, AsyncCallback<List<FileModel>> callback)
                {
                    if (loadConfig == null)
                    {
                        FolderModel lMapA = new FolderModel("Map A", "");
                        lMapA.setId(getNewId());
                        FolderModel lMapB = new FolderModel("Map B", "");
                        lMapB.setId(getNewId());
                        FolderModel lMapC = new FolderModel("Map C", "");
                        lMapC.setId(getNewId());
    
    
                        callback.onSuccess(new ArrayList<FileModel>(Arrays.asList(lMapA, lMapB, lMapC)));
                    }
                    else
                    {
                        if (loadConfig.getName().equals("Map A"))
                        {
                            callback.onSuccess(mMapA);
                        }
                        else if (loadConfig.getName().equals("Map B"))
                        {
                            callback.onSuccess(mMapB);
                        }
                        else if (loadConfig.getName().equals("Map C"))
                        {
                            callback.onSuccess(mMapC);
                        }
                    }
                }
            };
    
    
            mLoader = new TreeLoader<FileModel>(proxy)
            {
                @Override
                public boolean hasChildren(FileModel parent)
                {
                    return parent instanceof FolderModel;
                }
            };
    
    
            FileModelProperties props = GWT.create(FileModelProperties.class);
    
    
            TreeStore<FileModel> store = new TreeStore<FileModel>(props.key());
            mLoader.addLoadHandler(new ChildTreeStoreBinding<FileModel>(store));
    
    
            ColumnConfig<FileModel, String> cc1 = new ColumnConfig<FileModel, String>(props.name(), 100, "Name");
    
    
            ColumnConfig<FileModel, Date> cc2 = new ColumnConfig<FileModel, Date>(props.lastModified(), 100, "Date");
            cc2.setCell(new DateCell(DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_MEDIUM)));
    
    
            ColumnConfig<FileModel, Long> cc3 = new ColumnConfig<FileModel, Long>(props.size(), 100, "Size");
    
    
            List<ColumnConfig<FileModel, ?>> l = new ArrayList<ColumnConfig<FileModel, ?>>();
            l.add(cc1);
            l.add(cc2);
            l.add(cc3);
    
    
            ColumnModel<FileModel> cm = new ColumnModel<FileModel>(l);
    
    
            FramedPanel cp = new FramedPanel()
            {
                @Override
                protected void onAfterFirstAttach()
                {
                    super.onAfterFirstAttach();
                    mLoader.load();
                }
            };
    
    
            cp.setHeadingText("Async TreeGrid");
            cp.setButtonAlign(BoxLayoutContainer.BoxLayoutPack.CENTER);
            cp.setPixelSize(600, 300);
    
    
            mTree = new TreeGrid<FileModel>(store, cm, cc1);
            mTree.setBorders(true);
            mTree.setTreeLoader(mLoader);
            mTree.getView().setTrackMouseOver(false);
            mTree.getView().setAutoExpandColumn(cc1);
    
    
            cp.setWidget(mTree);
    
    
            mLoader.addLoaderHandler(new LoaderHandler<FileModel, List<FileModel>>()
            {
                private boolean loadingExpandedNode = false;
    
    
                @Override
                public void onBeforeLoad(BeforeLoadEvent<FileModel> event)
                {
                    assert loadingExpandedNode == false : "Some node already loading, need to rethink using a single boolean";
                    FileModel parent = event.getLoadConfig();
                    if (parent == null)
                    {
                        return;
                    }
    
    
                    if (mTree.isExpanded(parent))
                    {
                        mTree.setExpanded(parent, false);
                        mTree.getTreeStore().removeChildren(parent);
                        loadingExpandedNode = true;
                    }
                }
    
    
                @Override
                public void onLoad(LoadEvent<FileModel, List<FileModel>> event)
                {
                    if (loadingExpandedNode)
                    {
                        mTree.setExpanded(event.getLoadConfig(), true);
                        loadingExpandedNode = false;
                    }
                }
    
    
                @Override
                public void onLoadException(LoadExceptionEvent<FileModel> event)
                {
                    if (loadingExpandedNode)
                    {
                        mTree.setExpanded(event.getLoadConfig(), true);
                        loadingExpandedNode = false;
                    }
                }
            });
    
    
            BorderLayoutContainer lBorderLayoutContainer = new BorderLayoutContainer();
            lBorderLayoutContainer.setCenterWidget(cp);
    
    
            ToolBar lToolBar = new ToolBar();
            TextButton lDeleteButton = new TextButton("DELETE");
            lDeleteButton.addClickHandler(getButtonClickHandler());
            TextButton lCopyButton = new TextButton("COPY");
            lCopyButton.addClickHandler(getButtonClickHandler());
            TextButton lPasteButton = new TextButton("PASTE");
            lPasteButton.addClickHandler(getButtonClickHandler());
    
    
            lToolBar.add(lCopyButton);
            lToolBar.add(lPasteButton);
            lToolBar.add(lDeleteButton);
    
    
            ContentPanel lToolbarPanel = new ContentPanel();
            lToolbarPanel.add(lToolBar);
    
    
            lBorderLayoutContainer.setNorthWidget(lToolbarPanel);
    
    
            TreeGridDragSource<FileModel> lGridDragSource = getGridDragSource();
            MyTreeGridDropTarget lGridDropTarget = getGridDropTarget();
    
    
            return lBorderLayoutContainer;
        }
    
    
        ///////////////////////////
        // Arcus-Solutions hacking
        ///////////////////////////
        private String getNewId()
        {
            return new Integer(mNewId++).toString();
        }
    
    
        private ClickHandler getButtonClickHandler()
        {
            if (mButtonSelectHandler == null)
            {
                mButtonSelectHandler = new ClickHandler()
                {
                    @Override
                    public void onClick(ClickEvent pEvent)
                    {
                        TextButton lTextButton = (TextButton) pEvent.getSource();
                        fireButtonSelection(lTextButton);
                    }
                };
            }
    
    
            return mButtonSelectHandler;
        }
    
    
        private void fireButtonSelection(TextButton pTextButton)
        {
            if (pTextButton.getText().equals("DELETE"))
            {
                FileModel lDeleteItem = mTree.getSelectionModel().getSelectedItem();
    
    
                if (!(lDeleteItem instanceof FolderModel))
                {
                    mMapA.remove(lDeleteItem);
                    mMapB.remove(lDeleteItem);
                    mMapC.remove(lDeleteItem);
                    mLoader.load();
                }
            }
            else if (pTextButton.getText().equals("COPY"))
            {
                mClipboardItem = mTree.getSelectionModel().getSelectedItem();
    
    
                if (mClipboardItem instanceof FolderModel)
                {
                    mClipboardItem = null;
                }
            }
            else if (pTextButton.getText().equals("PASTE"))
            {
                mPasteParentItem = mTree.getSelectionModel().getSelectedItem();
    
    
                if (mPasteParentItem instanceof FolderModel)
                {
                    if (mClipboardItem != null)
                    {
                        String lNewName = "Copy of " + mClipboardItem.getName();
                        FileModel lCopyDoc = new FileModel(lNewName, "");
                        lCopyDoc.setId(getNewId());
    
    
                        if (mPasteParentItem.getName().equals("Map A"))
                        {
                            mMapA.add(lCopyDoc);
                        }
    
    
                        if (mPasteParentItem.getName().equals("Map B"))
                        {
                            mMapB.add(lCopyDoc);
                        }
    
    
                        if (mPasteParentItem.getName().equals("Map C"))
                        {
                            mMapC.add(lCopyDoc);
                        }
    
    
                        Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand()
                        {
                            @Override
                            public boolean execute()
                            {
                                mLoader.load(mPasteParentItem);
                                return false;
                            }
                        }, 1000);
                    }
                }
            }
        }
    
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        protected void fireDropTargetEvent(List<FileModel> pMovedItemList, FileModel pParentItem)
        {
            for (FileModel lItem : pMovedItemList)
            {
                mMapA.remove(lItem);
                mMapB.remove(lItem);
                mMapC.remove(lItem);
    
    
                FileModel lMovedItem = new FileModel();
                lMovedItem.setId(lItem.getId());
                lMovedItem.setName(lItem.getName());
    
    
                if (pParentItem.getName().equals("Map A"))
                {
                    mMapA.add(lMovedItem);
                }
                else if (pParentItem.getName().equals("Map B"))
                {
                    mMapB.add(lMovedItem);
                }
                else if (pParentItem.getName().equals("Map C"))
                {
                    mMapC.add(lMovedItem);
                }
    
    
                mTree.getTreeStore().update(lMovedItem);
            }
        }
    
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        protected TreeGridDragSource<FileModel> getGridDragSource()
        {
            if (mGridDragSource == null)
            {
                mGridDragSource = new TreeGridDragSource<FileModel>(mTree);
            }
    
    
            return mGridDragSource;
        }
    
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        protected MyTreeGridDropTarget getGridDropTarget()
        {
            if (mGridDropTarget == null)
            {
                mGridDropTarget = new MyTreeGridDropTarget(mTree);
                mGridDropTarget.setFeedback(com.sencha.gxt.dnd.core.client.DND.Feedback.APPEND);
                mGridDropTarget.setAddChildren(true);
                mGridDropTarget.setAllowSelfAsSource(true);
            }
    
    
            return mGridDropTarget;
        }
    
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        public interface FileModelProperties extends PropertyAccess<FileModel>
        {
            @Editor.Path("id")
            ModelKeyProvider<FileModel> key();
    
    
            ValueProvider<FileModel, String> name();
    
    
            ValueProvider<FileModel, String> path();
    
    
            ValueProvider<FileModel, Date> lastModified();
    
    
            ValueProvider<FileModel, Long> size();
        }
    
    
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        private class MyTreeGridDropTarget extends TreeGridDropTarget<FileModel>
        {
            public MyTreeGridDropTarget(TreeGrid<FileModel> tree)
            {
                super(tree);
            }
    
    
            protected void handleAppendDrop(DndDropEvent pDndDropEvent, Tree.TreeNode<FileModel> pParentTreeNode)
            {
                super.handleAppendDrop(pDndDropEvent, pParentTreeNode);
    
    
                List<TreeStore.TreeNode<FileModel>> lMovedNodeList = (List<TreeStore.TreeNode<FileModel>>) pDndDropEvent.getData();
                mMovedItemList = new ArrayList<FileModel>(lMovedNodeList.size());
                mParentItem = null;
    
    
                if (pParentTreeNode != null)
                {
                    mParentItem = pParentTreeNode.getModel();
                }
    
    
                for (TreeStore.TreeNode<FileModel> lMovedNode : lMovedNodeList)
                {
                    FileModel llMovedItem = lMovedNode.getData();
                    mMovedItemList.add(llMovedItem);
                }
    
    
                if (mMovedItemList.size() > 0)
                {
                    // handles the data move on the server
                    Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand()
                    {
                        @Override
                        public boolean execute()
                        {
                            fireDropTargetEvent(mMovedItemList, mParentItem);
                            return false;
                        }
                    }, 1000);
                }
            }
        }
    }
    Start the code;

    startup.jpg

    Open MapB and Mapc

    MapB & MapC.jpg

    Drag'n Drop Doc3 into MapB

    Drop Doc3 in MapB.jpg

    Close and re-open mapB

    Error .jpg

    As you can see, this results in the same error as with the previous data update.
    It was necessary to add the asynchronous behaviour of my server to the code to reproduce the error;
    (line 424 to 433)

    Code:
                    Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand()
                    {
                        @Override
                        public boolean execute()
                        {
                            fireDropTargetEvent(mMovedItemList, mParentItem);
                            return false;
                        }
                    }, 1000);
    apart from this story; when a Map becomes empty, it is also not possible anymore to drop a new Doc into the empty Map...

    Empty MapC.jpg
    Last edited by rcbeuker; 8 Jan 2013 at 1:46 PM. Reason: New picture with empty map bug

  10. #20
    Sencha User
    Join Date
    May 2011
    Location
    Netherlands
    Posts
    45
    Vote Rating
    1
    rcbeuker is on a distinguished road

      0  

    Default


    Hello Colin,

    Any suggestion or workaround when using store.update? Did you already try the example code I posted 8 Jan?

    Regards,

    Roland

Thread Participants: 2