-
3 Dec 2012 2:42 PM #11
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.
-
4 Dec 2012 5:41 AM #12
Example code
Example code
Hello Colin,
The following example code reproduces the bug. it is a variation on the AsyncTreeGridExample;
Schermafbeelding 2012-12-04 om 2.16.53 PM.pngCode: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.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
-
4 Dec 2012 3:02 PM #13
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:
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!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); } });
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:
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.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; } } });
Hopefully this gets you past this bug. We'll update this thread when we have a fix available in the library itself.
-
5 Dec 2012 2:47 AM #14
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
This example places new files in open parents, isn't this possible with a data update from the server?
Regards,
Roland Beuker
-
5 Dec 2012 8:10 AM #15
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.
-
17 Dec 2012 12:54 PM #16
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
-
5 Jan 2013 4:50 AM #17
-
7 Jan 2013 8:15 AM #18
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.
-
8 Jan 2013 1:27 PM #19
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;
Start the code;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); } } } }
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)
apart from this story; when a Map becomes empty, it is also not possible anymore to drop a new Doc into the empty Map...Code:Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() { @Override public boolean execute() { fireDropTargetEvent(mMovedItemList, mParentItem); return false; } }, 1000);
Empty MapC.jpgLast edited by rcbeuker; 8 Jan 2013 at 1:46 PM. Reason: New picture with empty map bug
-
16 Jan 2013 11:50 AM #20
Hello Colin,
Any suggestion or workaround when using store.update? Did you already try the example code I posted 8 Jan?
Regards,
Roland
Success! Looks like we've fixed this one. According to our records the fix was applied for
EXTGWT-2644
in
3.0.4.


Reply With Quote