Opened 12 years ago
Closed 6 years ago
#11065 closed enhancement (patchwelcome)
Tree: option to reload/refresh
Reported by: | Florian | Owned by: | |
---|---|---|---|
Priority: | high | Milestone: | 1.13 |
Component: | Dijit | Version: | 1.4.2 |
Keywords: | Cc: | ||
Blocked By: | Blocking: |
Description
I already posted on dojo-interest (http://mail.dojotoolkit.org/pipermail/dojo-interest/2010-February/043151.html and http://mail.dojotoolkit.org/pipermail/dojo-interest/2010-April/044861.html), but I think the issue may be a bug, so this goes here too:
In my application I have a Tree with a ForestStoreModel? and a JsonRestStore? with a PHP-backend. As the data on the PHP-backend can be altered by other users of the application I want to give the users a possibility to manually "reload" the tree. Technically spoken I like to inform the tree of changes which were made to the store's backend. Kris Zyp told me that the only thing I have to do is to call a fetch() on the JsonRestStore?. If I do that, I can watch the call to the backend in Firebug, can see the altered data the backend delivers and the tree does ... nothing.
To demonstrate this behaviour I created a little example here: http://digicult-museen.de/test/tree-refresh-example.html . Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dijit/themes/soria/soria.css" /> <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/dojo.xd.js" type="text/javascript"></script> <script type="text/javascript"> dojo.require('dijit.Tree'); dojo.require('dojox.data.JsonRestStore'); </script> <script type="text/javascript"> var reloadStore = function() { store.fetch({ query: { parent: 0 } }); }; var loadTreeItem = function(item) { console.debug(item); }; dojo.addOnLoad(function() { dojo.parser.parse(); }); </script> </head> <body class="soria"> <span dojoType="dojox.data.JsonRestStore" jsId="store" target="/test/treeRootData.php" labelAttribute="name" idAttribute="id"> </span> <span dojoType="dijit.tree.ForestStoreModel" jsId="model" store="store" childrenAttr="children" query="{parent: 0}" rootId="0" rootLabel="" labelAttr="name" idAttribute="id" deferItemLoadingUntilExpand="true"> </span> <div dojoType="dijit.Tree" model="model" jsId="tree" showRoot="false" persist="false" copyOnly="true"> <script type="dojo/method" event="onClick" args="item"> loadTreeItem(item); </script> </div> <a href="javascript:reloadStore()">reload root by store.fetch()</a> <br> <a href="javascript:model._requeryTop()">reload root by model._requeryTop()</a> </body> </html>
Short summary: If the reload-link is clicked, the store fetches the two rootnodes again, which alternately deliver the following data:
[ { $ref: 'node1', name:'node1', someProperty:'somePropertyA', children:true}, { $ref: 'node2', name:'node2', someProperty:'somePropertyB'} ]
[ { $ref: 'node1', name:'node1', someProperty:'somePropertyA'}, { $ref: 'node2', name:'node2', someProperty:'somePropertyB', children:true} ]
Bug? Am I doing something wrong?
Regards,
Florian
Change History (16)
comment:1 Changed 12 years ago by
Milestone: | tbd → future |
---|---|
Summary: | reloading treestore with fetch() → Tree: option to reload/refresh |
Type: | defect → enhancement |
comment:2 Changed 12 years ago by
Thanks for the reply and for changing the summary!
For the simple thing in your reply: .get('path') only gives me the possibility to remember exactly one openend subnode (-> the last expanded), but a user usually has more than one openend path in a tree... How do I handle this?
comment:3 Changed 12 years ago by
Oh, the opened/closed state of each tree node is stored in a cookie, just use the persist=true flag.
comment:4 Changed 12 years ago by
I think I found a solution I can live with.
Code:
/** * * Extension to dijit.Tree to provide a reload-method * and prevent caching with JsonRestStore * * @author floriank * @date 2010/05/06 * @link http://www.digicult-sh.de/ * */ dojo.provide("digicult.dijit.Tree"); dojo.require("dijit.Tree"); dojo.declare("digicult.dijit.Tree", dijit.Tree, { /** * initialize the tree-persist-cookie to be aware of * tree-expands (necessary for refreshing of tree) */ onLoad: function(){ // clear and init the tree's persist-cookie dojo.cookie(this.cookieName, ''); this.attr('persist', true); this._initState(); }, /** * remember the original _loadObject-method in TreeNode to be * used in onOpen-event */ _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){ if(node._expandNodeDeferred && !recursive){ // there's already an expand in progress (or completed), so just return return node._expandNodeDeferred; // dojo.Deferred } item = node.item; if (item._loadObject && !node._loadObjectFunction) { node._loadObjectFunction = item._loadObject; } return this.inherited(arguments); }, /** * reset the _loadObject-method of the item to its default * to force a "fresh reload" at the next _expandNode() */ onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){ node.state = "UNCHECKED"; if (node._loadObjectFunction && !item._loadObject) { item._loadObject = node._loadObjectFunction; delete node._loadObjectFunction; } }, /** * reload the whole tree * * (many thanks to the tips from Rob Weiss found here: * http://mail.dojotoolkit.org/pipermail/dojo-interest/2010-April/045180.html) */ reload: function () { // delete the tree's itemNodes this._itemNodesMap = {}; // reset the state of the rootNode this.rootNode.state = "UNCHECKED"; // unset the tree.model's root-children this.model.root.children = null; // unset the JsonRestStore's cache storeTarget = this.model.store.target; for (var idx in dojox.rpc.Rest._index) { if (idx.match("^" + storeTarget)) { delete dojox.rpc.Rest._index[idx]; } } // remove the rootNode if (this.rootNode) { this.rootNode.destroyRecursive(); } // rebuild the tree this._load(); } });
I also updated the example on http://digicult-museen.de/test/tree-refresh-example.html .
comment:5 Changed 12 years ago by
Update: Additionally to the function to reload the whole tree it would be fine to have an option to reload all opened childnodes of one treenode. In my opinion the function reload() should have one paramter of type "TreeNode?" where you pass in either the tree's rootNode or one other node of the tree.
comment:6 follow-up: 8 Changed 12 years ago by
Hi all.
Just found this ticket. Is there really no way to refresh a branch in a tree (using ForestStoreModel? and JsonRestStore?)? I thought, JsonRestStore? was the recommended store for a tree? How else am I supposed to show updated data from the server in a tree? Is it not even planned to implement the reload/refresh for a branch of a tree?
Best regards, Ralph
comment:7 Changed 12 years ago by
There appears to be a possible solution, at least to some cases, here in a new mail list thread which referred back to this bug report:
http://mail.dojotoolkit.org/pipermail/dojo-interest/2010-August/048292.html
comment:8 Changed 12 years ago by
Replying to r.reckert:
Hi all.
Just found this ticket. Is there really no way to refresh a branch in a tree (using ForestStoreModel? and JsonRestStore?)? I thought, JsonRestStore? was the recommended store for a tree? How else am I supposed to show updated data from the server in a tree? Is it not even planned to implement the reload/refresh for a branch of a tree?
Best regards, Ralph
Ralph, as I wrote above, Tree responds to notification (onNew, onChange, etc, see the dojo.data Notification API), but that requires the store to be aware of what elements changed and then notify the Tree.
Various people have asked for an option to reload the Tree (refetching all data), but I don't see a ticket for it so I'll use this one.
However, the simple thing to do is just to recreate your tree. get('path') on the old tree followed by set('path', ...) on the new tree will restore it to it's old position.
comment:9 Changed 12 years ago by
The above tree example seems to be broken on 1.5 using FF 3.6.10 on WinXP Pro 32bit with a JsonRestStore?:
[Widget digicult.dijit.Tree, digicult_dijit_Tree_0] { _attachPoints=, more...} : error loading root children: TypeError?: args.item is undefined { message="args.item is undefined", more...} bootstrap.js (line 1824) TypeError?: args.item is undefined [Break on this error] (676 out of range 505) bootstrap.js (line 676) TypeError?: args.item is undefined [Break on this error] (676 out of range 505) bootstrap.js (line 676)
The error happens after this._load is called on the last line in Tree.reload():
rebuild the tree this._load();
}
});
I can confirm the tree works perfectly in 1.4.3, but will not reload on 1.5.
comment:10 Changed 12 years ago by
Hi Tourman,
I am working with this updated digicult.dijit.Tree (but I still hope for an 'official' refresh in dijit.Tree):
/** * * Extension to dijit.Tree to provide a reload-method * and prevent caching with JsonRestStore * * @author floriank * @date 2010/07/30 * @link http://www.digicult-sh.de/ * */ dojo.provide("digicult.dijit._TreeNode"); dojo.provide("digicult.dijit.Tree"); dojo.require("dijit.Tree"); dojo.declare("digicult.dijit._TreeNode", dijit._TreeNode, { _loadObjectFunction: null }); dojo.declare("digicult.dijit.Tree", dijit.Tree, { /** * initialize the tree-persist-cookie to be aware of * tree-expands (necessary for refreshing of tree) */ onLoad: function(){ // clear and init the tree's persist-cookie if (!this.get('persist')) { dojo.cookie(this.cookieName, ''); this.set('persist', true); this._initState(); } }, /** * remember the original _loadObject-method in TreeNode to be * used in onOpen-event */ _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){ if(node._expandNodeDeferred && !recursive){ // there's already an expand in progress (or completed), so just return return node._expandNodeDeferred; // dojo.Deferred } item = node.item; if (item._loadObject && !node._loadObjectFunction) { node._loadObjectFunction = item._loadObject; } return this.inherited(arguments); }, _createTreeNode: function(/*Object*/ args){ // summary: // creates a TreeNode // description: // Developers can override this method to define their own TreeNode class; // However it will probably be removed in a future release in favor of a way // of just specifying a widget for the label, rather than one that contains // the children too. return new digicult.dijit._TreeNode(args); }, /** * reset the _loadObject-method of the item to its default * to force a "fresh reload" at the next _expandNode() */ onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){ node.state = "UNCHECKED"; if (node._loadObjectFunction && !item._loadObject) { item._loadObject = node._loadObjectFunction; delete node._loadObjectFunction; } }, /** * reload the whole tree * * (many thanks to the tips from Rob Weiss found here: * http://mail.dojotoolkit.org/pipermail/dojo-interest/2010-April/045180.html) */ reload: function () { // remember path path = this.get('path'); // remove the rootNode if (this.rootNode) { this.rootNode.destroyRecursive(); } // unset the JsonRestStore's cache storeTarget = this.model.store.target; for (var idx in dojox.rpc.Rest._index) { if (idx.match("^" + storeTarget)) { delete dojox.rpc.Rest._index[idx]; } } // reset the state of the rootNode this.rootNode.state = "UNCHECKED"; // reset the tree.model's root this.model.constructor(this.model); // rebuild the tree this.postMixInProperties(); this._load(); this.set('path', path).then( // focus node after setPath has finished dojo.hitch(this, function() { this.focusNode(this.get('selectedNode')); } )); } });
Regards,
Florian
comment:11 Changed 11 years ago by
Update for compatibility with 1.6 (plus some minor changes for modularity). To be able to reload just use de.domcura.dijit.RefreshableTree? / de.domcura.dijit.RefreshableUncachedTree? instead of dijit.Tree and call .reload() on it.
To dijit.Tree-developers: Are there any plans for an 'official' support for reloading a dijit.Tree in nearer future?
/** * package de.domcura.dijit.RefreshableTree * * Extension to dijit.Tree to provide a reload-method * * @author floriank * @date 2011/06/16 * @link http://www.domcura-ag.de/ */ dojo.provide('de.domcura.dijit.RefreshableTree'); dojo.provide('de.domcura.dijit._UncachedTreeNode'); dojo.provide('de.domcura.dijit.RefreshableUncachedTree'); dojo.require('dijit.Tree'); dojo.declare('de.domcura.dijit.RefreshableTree', [dijit.Tree], { _reloadPaths: null, _reloadOnLoadConnect: null, /** * initialize the tree-persist-cookie to be aware of * tree-expands (necessary for refreshing of tree) */ onLoad: function(){ this.inherited(arguments); // clear and init the tree's persist-cookie if (!this.get('persist')) { dojo.cookie(this.cookieName, ''); this.set('persist', true); this._initState(); } }, /** * Unset tree's attributes but leave the widget untouched. * This code is copied from dijit.Tree's .destroy()-method. * * dijit.Tree.destroy() could be reduced to * * destroy: function() { * this._destroy(); * this.inherited(arguments); * } * * if someone of the dijit.Tree-developers can * implement this ._destroy()-method. * **/ _destroy: function(){ if(this._curSearch){ clearTimeout(this._curSearch.timer); delete this._curSearch; } if(this.rootNode){ this.rootNode.destroyRecursive(); } if(this.dndController && !dojo.isString(this.dndController)){ this.dndController.destroy(); } this.rootNode = null; }, /** * reload the whole tree * * (many thanks to the tips from Rob Weiss found here: * http://mail.dojotoolkit.org/pipermail/dojo-interest/2010-April/045180.html) */ reload: function () { /* remember current paths: * simplify all nodes of paths-array to strings of identifiers because * after reload the nodes may have different ids and therefore the * paths may not be applied */ this._reloadPaths = dojo.map(this.get('paths'), dojo.hitch(this, function(path) { return dojo.map(path, dojo.hitch(this, function(pathItem) { return this.model.getIdentity(pathItem) + ''; })); })); /* reset tree: */ this._destroy(); this.dndController = "dijit.tree._dndSelector"; /* unset the store's cache (if existing). * TODO: currently only tested on JsonRestStore! */ if (dojox && dojox.rpc && dojox.rpc.Rest && dojox.rpc.Rest._index) { for (idx in dojox.rpc.Rest._index) { if (idx.match("^" + this.model.store.target)) { delete dojox.rpc.Rest._index[idx]; } }; } // reset the tree.model's root this.model.constructor({ rootId: '0', rootLabel: '' }); // rebuild the tree this.postMixInProperties(); this.postCreate(); /* reset the paths */ this._reloadOnLoadConnect = dojo.connect(this, 'onLoad', dojo.hitch(this, function() { /* restore old paths. * FIXME: this will result in an error if a formerly selected item * is no longer existent in the tree after reloading! */ this.set('paths', this._reloadPaths).then(dojo.hitch(this, function() { if (this.get('selectedNode')) { this.focusNode(this.get('selectedNode')); } this._reloadPaths = null; dojo.disconnect(this._reloadOnLoadConnect); this._reloadOnLoadConnect = null; })); })); } }); /** * FIXME: This "uncached-behaviour" is simply copied from * digicult.dijit.Tree from 2010/07/30 and mostly untested!!! * (there is currently no usage of lazy-load in my projects) */ dojo.declare('de.domcura.dijit._UncachedTreeNode', dijit._TreeNode, { _loadObjectFunction: null }); dojo.declare('de.domcura.dijit.RefreshableUncachedTree', de.domcura.dijit.RefreshableTree, { /** * remember the original _loadObject-method in TreeNode to be * used in onOpen-event */ _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){ if(node._expandNodeDeferred && !recursive){ // there's already an expand in progress (or completed), so just return return node._expandNodeDeferred; // dojo.Deferred } if (node.item._loadObject && !node._loadObjectFunction) { node._loadObjectFunction = node.item._loadObject; } return this.inherited(arguments); }, _createTreeNode: function(/*Object*/ args){ // summary: // creates a TreeNode // description: // Developers can override this method to define their own TreeNode class; // However it will probably be removed in a future release in favor of a way // of just specifying a widget for the label, rather than one that contains // the children too. return new de.domcura.dijit._UncachedTreeNode(args); }, /** * reset the _loadObject-method of the item to its default * to force a "fresh reload" at the next _expandNode() */ onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){ node.state = "UNCHECKED"; if (node._loadObjectFunction && !item._loadObject) { item._loadObject = node._loadObjectFunction; delete node._loadObjectFunction; } } });
Regards,
Florian
comment:12 follow-up: 14 Changed 10 years ago by
Has anyone tested this with dojo 1.8?
we are using this modification in a tree widget with is declarative defined. Now we have the problem, that connected onClick functions are connected again for each refresh.
i.e: loading tree, clicking tree node -> normal behaviour refreshing tree, clicking tree node -> onClick function is executed twice refreshing tree again, clicking tree node -> onClicck function is executed three times
this happens regardless if I use dojo/on, dojo/connect or dojo/method in the markup
comment:13 Changed 10 years ago by
I didn't read through all that RefreshableTree code, but presumably something changed because Tree events like clicking are now handled at the root level, via event delegation.
comment:14 Changed 9 years ago by
Replying to bjoerns:
Has anyone tested this with dojo 1.8?
we are using this modification in a tree widget witch is declarative defined. Now we have the problem, that connected onClick functions are connected again for each refresh.
Yes, this is due to the fact that postCreate() gets called during reload, and in postCreate(), event handlers are registered.
comment:15 Changed 6 years ago by
Milestone: | future → 1.12 |
---|
We should either consider and land something like this for 1.12, or close as patchwelcome and encourage creation of an updated patch.
comment:16 Changed 6 years ago by
Resolution: | → patchwelcome |
---|---|
Status: | new → closed |
Closing as patchwelcome. If someone submits a PR including automated tests, will definitely consider it, but not going to work on it otherwise.
It's not a bug, calling fetch() on the store isn't supposed to have any effect on the Tree. Tree responds to notification (onNew, onChange, etc, see the dojo.data Notification API), but that requires the store to be aware of what elements changed and then notify the Tree.
Various people have asked for an option to reload the Tree (refetching all data), but I don't see a ticket for it so I'll use this one.
However, the simple thing to do is just to recreate your tree. get('path') on the old tree followed by set('path', ...) on the new tree will restore it to it's old position.