Login Register

dijit.Tree and dojo.data in Dojo 1.1 (model)

I've done some work on dijit.Tree since my last blog post. (These changes that went into the 1.1 beta 2 release.) Basically, decided that it was too difficult to force everyone to present a dojo.data model for the tree's data, especially w.r.t how to query the children items (and getting notifications about changes to the children items). For dijit.Tree it really makes to have a getChildren() asynchronous method, rather than the dojo.data model where there's a synchronous call to get children stubs, and then an asynchronous loadItem() call for each stub.

So... I've created an interface layer between the tree and dojo.data. There are two implementations of this model, both at the end of Tree.js, dijit.tree.TreeStoreModel and dijit.tree.ForestStoreModel. dijit.tree.TreeStoreModel connects to a dojo.data store which contains a single root item (see the "Foods" item in the categories.json, and dijit.tree.ForestStoreModel connects to stores with no root item, fabricating a root item for the tree.

Here's a revised version of my original post, reflecting the recent changes:


In version 1.1 I've fixed the Tree architecture to be able to handle DnD correctly. This involved adding an interface layer between dojo.data and the Tree, to handle some impedance mismatches between them, especially for the case where the data store doesn't contain a single root item corresponding to the root node in the tree.

Tree/data store connection

Dijit.Tree's connection to dojo.data gives the tree a lot of power, but it's also complicated the API to something more than if the tree simply displayed a read-only collapsible list, and/or had it's own custom data format.

The dijit.Tree widget is a view on a dojo.data store. Tree's job is to display the store data in a hierarchy. This is more complicated then it sounds because a store doesn't represent a tree per-se, but rather it just contains a bunch of items, some of which contain references to other items. For example, an item might have multiple children attributes, each of which points to other items. Multiple parent items could point to the same child item, or there could even be loops.

Consider this store:

store.png

If we represented that data in ItemFileWriteStore, it might look like the JSON below, although it's important to realize that Tree can interface to any store, so the internals of the store are irrelevant.

{
        identifier: 'id',
        label: 'name',
        items: [
                { id: '0', name: 'Fruits', top: true, children: [ {_reference: 1}, {_reference: 4} ] },
                { id: '1', name: 'Citrus', items: [ {_reference: 2}, {_reference: 3} ] },
                { id: '2', name: 'Orange'},
                { id: '3', name: 'Lemon'},
                { id: '4', name: 'Tomato'},
                { id: '5', name: 'Vegetables', top: true, children: [ {_reference: 4} ] },
                { id: '6', name: 'Lettuce'},
        ]
}

We'd like the dijit.Tree widget to display those items above in a hierarchy, like this:

  • Fruits
    • Citrus
      • Orange
      • Lemon
    • Tomato
  • Vegetables
    • Tomato

A few things to note:

  • this is technically a forest, not a tree, since there are multiple top level nodes.
  • there's only one 'Tomato' item, but it's referenced as both a Fruit and a Vegetable.

Tree doesn't support multiple-parent items yet but will in the future. The point is to note how a data store represents very general data which we are mapping into a Tree.

Dojo 1.0 Tree API

In the dojo 1.0 release, dijit.Tree featured an API where you specify a query to get the top-level item or items, and then an attribute to query to get children of those top level items. For this data you would want to display all the elements marked as top:true at the top level, and then get children via the "children" attribute.

<div dojoType="dijit.Tree" store="catStore" query="{top: true}"
		labelAttr="name" childrenAttr="children"></div>

Tree also provided an option to create a root level node to tie 'Fruits' and 'Vegetables' together without any such item existing in the data store, by specifying a label to the Tree:

<div dojoType="dijit.Tree" store="catStore" query="{top: true}" label="Food"
		labelAttr="name" childrenAttr="children"></div>
which displays a tree like:
  • Food
    • Fruits
      • Citrus
        • Orange
        • Lemon
      • Tomato
    • Vegetables
      • Tomato

This is a convenient feature, although it causes some confusion since there's no data store item associated with the top level node in the tree, and thus clicking it will produce an onClick(null) rather than onClick(item). Of course you could add a "Food" item to your data store but it may not be convenient to change your data model just to affect the view (the Tree).

dijit.tree.model

Version 1.1 introduces a model layer between the Tree and dojo.data (or you could write your own model that doesn't even connect to dojo.data). A model is anything that presents this interface:

destroy: function(){
        // summary: destroys this object, releasing connections to the store
},
// =======================================================================
// Methods for traversing hierarchy
getRoot: function(onItem){
        // summary:
        //            Calls onItem with the root item for the tree, possibly a fabricated item.
        //            Throws exception on error.
},
mayHaveChildren: function(/*dojo.data.Item*/ item){
        // summary
        //            Tells if an item has or may have children.  Implementing logic here
        //            avoids showing +/- expando icon for nodes that we know don't have children.
        //            (For efficiency reasons we may not want to check if an element actually
        //            has children until user clicks the expando node)
},
getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
        // summary
        //           Calls onComplete() with array of child items of given parent item, all loaded.
        //            Throws exception on error.
},
// =======================================================================
// Inspecting items
getIdentity: function(/* item */ item){
        // summary: returns identity for an item
},
getLabel: function(/*dojo.data.Item*/ item){
        // summary: get the label for an item
},
// =======================================================================
// Write interface
newItem: function(/* Object? */ args, /*Item?*/ parent){
        // summary
        //            Creates a new item.   See dojo.data.api.Write for details on args.
},
pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy){
        // summary
        //            Move or copy an item from one parent item to another.
        //            Used in drag & drop.
        //            If oldParentItem is specified and bCopy is false, childItem is removed from oldParentItem.
        //            If newParentItem is specified, childItem is attached to newParentItem.
},
// =======================================================================
// Callbacks
onChange: function(/*dojo.data.Item*/ item){
        // summary
        //            Callback whenever an item has changed, so that Tree
        //            can update the label, icon, etc.   Note that changes
        //            to an item's children or parent(s) will trigger an
        //            onChildrenChange() so you can ignore those changes here.
},
onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
        // summary
        //            Callback to do notifications about new, updated, or deleted items.
}

TreeStoreModel

dijit provides a model called dijit.Tree.TreeStoreModel that connects to any datastore which contains a root item corresponding to the (possibly hidden) root node of the Tree.

var myStore = new dojo.data.ItemFileWriteStore({
        url: ...});
var myModel = new dijit.tree.TreeStoreModel({
        store: myStore,
        query: {top:true},
        rootId: "food",
        rootLabel: "Foods",
        childrenAttrs: ["children"]
});
var tree  new dijit.Tree({
        model: myModel
});

To interface to something like QueryReadStore, where there's a non-standard way of getting the children of an item, you would override TreeStoreModel.getChildren(). If your tree supported DnD you would also need to add code to call onChildrenChange() whenever the children of an item changed.

What if I don't have a single root item?

Given that a dijit.Tree is just a view onto a dojo.data store, any updates to the tree (ie, moving a node via DnD) have to be reflected to the store, and conversly any updates to the store need to be reflected in the tree.

sync.png

As a matter of fact, dragging-and-dropping (ie, moving) an item in the Tree doesn't update the tree directly, but rather just updates the data store, and then Tree responds to that update just like any update to the store (from an external source). So, there are two issues involved with a tree:

  1. notifying the data store about updates
  2. responding to changes in the store

If you move an item (for example, deciding that a lettuce is a fruit rather than a vegetable), the DnD code tells the data store that Vegetable's children attribute changed from [Tomato, Lettuce] to just [Tomato], and that 'Fruits' children attribute changed from [Citrus, Apple, Banana] to [Citrus, Apple, Banana, Lettuce] (or perhaps a different order). The data store then notifies tree of the updates to 'Fruits' and 'Vegetables' children attributes, and tree updates it's display.

If, however, you decided that a 'Tomato' was neither a 'Fruit' nor a 'Vegetable', but rather just a type of food, you would want to somehow make it a top level item in the tree so that myStore.fetch({ query: {top: true} }) returns 'Tomato' in it's query. We have two issues here, namely that:

  1. dijit.Tree doesn't know how to mark the element as "top level". In this case, it's simply setting the top attribute to true, but there may be more complicated cases.
  2. The data store notifies the Tree that an item has changed (namely that the item's "top" attribute has been set), but there's no way for Tree to know whether that item now matches the "top level items" query specified to Tree without rerunning the query. And even if it new an item was top level, it wouldn't know the order of the new item relative to the other items. This is because Tree can connect to any data store and thus the query format is a black box.

ForestStoreModel

In short, all of these problems are caused by allowing a query which lists a set of top level children, and whose results can change over time. If there was a single root item, specified when the Tree is constructed, everything would be much simpler.

But In order to support data stores which match this pattern, I've created dijit.tree.ForestStoreModel, which wraps a store and hides that complexity from the tree.

var myStore = new dojo.data.ItemFileWriteStore({
        url: ...});
var myModel = new dijit.tree.ForestStoreModel({
        store: myStore,
        query: {top:true},
        rootId: "food",
        rootLabel: "Foods",
        childrenAttrs: ["children"]
});
var myTree = new dijit.Tree({
        model: myModel
});

ForestStoreModel has a number of hooks to handle changes to elements when they are added/removed from the root node, such as setting top: true. See these two callbacks that the developer must override:

onAddToRoot: function(/* item */ item){
        // summary
        //            Called when item added to root of tree; user must override
        //            to modify the item so that it matches the query for top level items
        // example
        //      |     store.setValue(item, "root", true);
        console.log(this, ": item ", item, " added to root");
},
onLeaveRoot: function(/* item */ item){
        // summary
        //            Called when item removed from root of tree; user must override
        //            to modify the item so it doesn't match the query for top level items
        // example
        //      |    store.unsetAttribute(item, "root");
        console.log(this, ": item ", item, " removed from root");
}

The important thing is that ForestStoreModel presents a consistent interface to dijit.Tree such that every time an item is moved, onChildrenChange() is called.

Note that the top level item doesn't actually have to be displayed. The showRoot parameter controls whether the tree shows up as a true tree, like:

  • Food
    • Fruits
      • Citrus
        • Orange
        • Lemon
      • Tomato
    • Vegetables
      • Tomato

Or just as a forest:

  • Fruits
    • Citrus
      • Orange
      • Lemon
    • Tomato
  • Vegetables
    • Tomato

Deprecated APIs

The store parameter to Tree is deprecated in favor of the model parameter.

getItemChildren() and similar callbacks should now be specified on the model rather than on the Tree itself.

Nested ItemFileReadStore problems

Let's look at ItemFileWriteStore with nested elements since that's often used. Consider a data file like this:

{ 
	identifier: 'id',
	label: 'name',
	items: [
		{ id: '0', name: 'Fruits', children: [
			{ id: '1', name: 'Citrus', items: [
				{ id: '2', name: 'Orange'}
				{ id: '3', name: 'Lemon'}
				...
			]},
			{ id: '4', name: 'Apple'},
			{ id: '5', name: 'Banana'}
			...
		]},
		{ id: '6', name:'Vegetables', children:[
			{ id: '7', name: 'Tomato'},
			{ id: '8', name: 'Lettuce'}
			...
		]},
		...
	]
}

How do we mark 'Tomato' as a "top level" item, so that ItemFileReadStore.fetch() will return that item? Short of actually deleting and recreating the item, which is bad for a number of reasons, there's no way to do that with ItemFileReadStore and nested data. The best we can do is switch to a referenced layout like at the beginning of this article. Although there's a special API to create an item as a child of another item, there's no API to move it to be top level.

Actually, the nested ItemFileReadStore has another limitation that you can't multi-parent an item, such as marking 'Tomato' as both a fruit and a vegatable.

AttachmentSize
decorated.png17.36 KB
store.png15.29 KB
sync.png6.43 KB

Thanks for the excellent doc

I'm nitpicking... why the concats? This form would be shorter:

this.connects = [
	dojo.connect(store, "onNew", this, "_onNewItem"),
	dojo.connect(store, "onDelete", this, "_onDeleteItem"),
        dojo.connect(store, "onSet", this, "_onSetItem")
];

hmm

Hmm, I guess you are right :-). Probably previously (before the refactoring) there were some other connects that got added to that array too. I don't see anything now though, not even in the ForestStoreModel.

Very Clear !

Hi Bill

Your clear Instructions and examples make the process of upgrading from the old method to the new, very easy !.
I notice that the tree is one of the more dynamic areas. Is anywhere in the site, the plans of the future V2.0
tree API or functionality ?
Please continue with your blog, it helps a lot.

Eduardo

thanks

Thanks for the support :-). I don't have any documents specifically about Tree (the one enhancement that comes to mind is #5719, which opens the way for things like editable tree nodes... whatever features we add I want to be careful that Tree doesn't become gigantic like in 0.4... I think the code was 75 printed pages.

As for general plans, the only thing I have written is this roadmap, which ends at 1.1 (although there are a few things on there we didn't get to). After 1.1 is released will be thinking more about future dijit features.

A small patch to fix Labels in TreeStoreModel

Bill, thanks for the excellent write-up. I've been able to hit the floor running with this new info.

While working with the dijit.Tree using a ForestStoreModel, I found I needed to override the "label" attribute of the underlying DataStore, but neither the dijit.Tree nor the TreeStoreModel had support for overriding labelAttr under this new configuration without implementing a complete getLabel() function.

Thus, I added support for labelAttr on the dijit.Tree.TreeStoreModel. I didn't add to dijit.Tree, as the code would have needed to do something like "this.model.store.getvalue(item,this.labelAttr)" to fetch the overridden value -- which would break the separation of the Tree from the underlying DataSource.

Anyway, here's what I'm using, and I hope something like this can be helpful for others. :-)

Index: Tree.js
===================================================================
--- Tree.js     (revision 12793)
+++ Tree.js     (working copy)
@@ -917,6 +917,7 @@
        // childrenAttr: String
        //              one ore more attributes that holds children of a tree node
        childrenAttr: ["children"],
+       labelAttr: "",
 
        // root: dojo.data.Item
        //              Pointer to the root item (read only, not a parameter)
@@ -1040,7 +1041,11 @@
 
        getLabel: function(/*dojo.data.Item*/ item){
                // summary: get the label for an item
-               return this.store.getLabel(item);       // String
+               if (this.store.hasAttribute(item,this.labelAttr)) {
+                       return this.store.getValue(item,this.labelAttr);
+               } else {
+                       return this.store.getLabel(item);       // String
+               }
        },
 
        // =======================================================================

getLabel() not working

Hi lalee,

You might want to make the if() condition be if(labelAttr) so that TreeStoreModel calls store.getLabel() unless the user has overridden that behavior by specifying a label attribute. I'm curious though why you wanted this feature. It's easy to make getLabel() work for ItemFileReadStore. Are you using a different store? Or maybe the label of your tree nodes is different than the official label of the items?

Labels Not Working

Hi Bill,

Actually, I am using my own DataStore to communicate with Rails, primarily to support lazy-loading of Grids and Trees.

In the Tree test examples (test_Tree.html), I noticed the dijit.Tree declarations have childrenAttr and labelAttr. I couldn't locate the code that implemented the labelAttr override in Tree.js, either at the dijit.Tree or the dijit.Tree.TreeStoreModel level, and figured it was either overlooked, or remnants of a removed feature.

Instead of requiring users to implement a complete getLabel() at the Tree or TreeStoreModel to override the store's label (if the store has one defined), I figured it's a common use case for the programmer to provide a column-override at the TreeStoreModel level if they choose to do so. Supplying it at the TreeStoreModel makes most sense, as it's an abstraction layer between the Tree and the underlying Store.

My thinking is that the underlying DataStore can be shared by multiple widgets, and the widgets may not always agree on how to interpret the data for display.

sounds reasonable

Sounds reasonable, I filed #6065 to do this in 1.2. I'll also remove the labelAttr from the test files as it's not being used currently.

Actually.. I just realized

Actually.. I just realized that the TreeStoreModel isn't a complete isolation layer, as there is no getValue() or getValues() -- the Tree would still need to directly reference model.store to get anything other than the Identity and Label. (I ported some old test-harness stuff that tried to reference additional columns in an onClick handler -("You clicked on [node-id] with [title] and [content]") and ran into this.

Maybe I'm confused about the intent of adding the TreeStoreModel as an interface layer. Should I be looking at it as an isolation layer, or more of a "Super DataSet" mixin specific to Trees?

accessing other values

That's true, there is no general getValues() call. I did it that way to keep the API as simple as possible, but it is arguable. It seems though that to really implement the full dojo.data API would be overwhelming and counterproductive. I can see simple hasAttribute/getValue APIs though. OTOH maybe for some cases item is a simple JS object and (custom user code) will want to access it directly.

I think that a great

I think that a great solution to this would be to move this construct into dojo.data.api - create a dojo.data.api.Heirarchy which contains the interface that you specified above.

That way, data stores can choose whether or not they want to implement dojo.data.api.Heirarchy.

Also, tree (and other widgets) that would like to access heirarchical data can do so in a consistent manner. In addition, one could assume that if a store doesn't implement Heirarchy, it can't be tied to a tree, if it doesn't implement BOTH Heirarchy and Write, then the resulting tree can't be draggable.

It also unties the model from the tree explicitly, and makes it possible for development of other "tree-like" widgets (like a store-driven menu structure, for example).

Working Example?

a working example would really be great for all the people who are not as familiar with the dojo framework.

for example, this seems to be working code, but does nothing.
(location: dojo-release-1.1.0b2-src/dijit/tests/test_Forest.html)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
                "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
        <title>Dijit Forest Test</title>
</head>
<body>
        <h1 class="testTitle">Dijit Forest Test</h1>
</body>

   <style type="text/css">
      @import "../../dojo/resources/dojo.css";
      @import "css/dijitTests.css";
   </style>

   <script type="text/javascript" src="../../dojo/dojo.js"
      djConfig="parseOnLoad: true, isDebug: true">
</script>
   <script type="text/javascript" src="_testCommon.js"></script>

   <script language="JavaScript" type="text/javascript">
      dojo.require("dojo.data.ItemFileReadStore");
      dojo.require("dojo.data.ItemFileWriteStore");
      dojo.require("dijit.Tree");     
      dojo.require("dijit.ColorPalette");
      dojo.require("dijit.Menu");
      dojo.require("dojo.parser");  // scan page for widgets and instantiate them
   </script>

   <script>
           var myStore = new dojo.data.ItemFileWriteStore({url:'_data/countries.json'});
           var myModel = new dijit.tree.ForestStoreModel({
              store: myStore,
         query: {type:'continent'},
              rootId: "earth",
              rootLabel: "Earth",
              childrenAttr: "children"
           });
           var tree = new dijit.Tree({
              model: myModel,
              childrenAttr: "children"
           });
   </script>


And if I run this in my firebug console after the page loads...

document.body.appendChild(tree.domNode)


... I get something, but still, its obviously not what anyone would want

Working Example

rheaghen,

Were you looking for something easier to digest, like this? (Sorry for the Rails-ish link tags)

<head>
<title>Dojo: Hello World!</title> 
<link href="/javascripts/dojo-trunk/dijit/themes/tundra/tundra.css?1204261573" media="screen" rel="stylesheet" type="text/css" />
<link href="/javascripts/dojo-trunk/dojo/resources/dojo.css?1204261483" media="screen" rel="stylesheet" type="text/css" />
<style type="text/css">
        html, body, #ScreenLayout {
                width: 100%; height: 100%;
                border: 0; padding: 0; margin: 0;
        }
</style>
<script djConfig="parseOnLoad:true, isDebug:true" src="/javascripts/dojo-trunk/dojo/dojo.js?1204470566" type="text/javascript"></script>
</head>
<body class="tundra">
<!-- test1.html.erb BEGINS -->
<h1 class="testTitle">Dijit Forest Test</h1>
<script language="JavaScript" type="text/javascript">
        dojo.require("dojo.data.ItemFileWriteStore");
        dojo.require("dijit.Tree");
</script>

<div dojoType="dojo.data.ItemFileWriteStore" jsId="myTreeStore" url="/javascripts/dojo-trunk/dijit/tests/_data/countries.json" handleAs="json-comment-filtered"></div>
<div dojoType="dijit.tree.ForestStoreModel"  jsId="myTreeModel" store="myTreeStore" rootId="earth" rootLabel="Earth" query="{type:'continent'}" childrenAttr="children" labelAttr="name"></div>
<div dojoType="dijit.Tree" id="myTreeView" model="myTreeModel"></div>

<!-- test1.html.erb ENDS -->
</body>

nightly tests

You can also look at the nightly tests like test_Tree.html.

Working Example?

This seems to work nicly, but I would like to see a programatic example if someone could provide it =)

programmative test?

I think the question is more:
"why it works in the declarative way (test_Tree.html), and seems not working in the programmative way with the example posted here above?"

startup

You probably just forgot tree.startup(). I checked in a programmatic example in test_Tree_Programmatic.html.

startup

I'm pretty sure I tried tree.startup() as well, but still got nothing.

Thanks Bill, we have to take

Thanks Bill, we have to take care to include the startup(), that's true, and the example is great and usefull.
But at least for me (on firefox 2.0.0.12, FC8) it does not display the nested children even in your example. Only the continent are displayed as leaves, and therefor are not clickeable. So no countries ...
Any idea about this strange behaviour.

Here below the test with

Here below the test with both declarative and programmatic approaches with the same type of Tree and data.
Is there still something missing to let the continents being "expandable" in the programmatic form?



	Dijit Tree Programmatic Test

	
		@import "../../../dojo/resources/dojo.css";
		@import "../css/dijitTests.css";
	

	
	

	
		dojo.require("dojo.data.ItemFileWriteStore");
		dojo.require("dojo.data.ItemFileReadStore");
		dojo.require("dijit.Tree");
		dojo.require("dojo.parser");	

		dojo.addOnLoad(function(){
 			var myStore = new dojo.data.ItemFileWriteStore({url:'../_data/countries.json'});
			var myModel = new dijit.tree.ForestStoreModel({
				store: myStore,
				query: {type:'continent'},
				rootId: "earth",
				rootLabel: "Earth",
				childrenAttr: "children"
			});
			var tree = new dijit.Tree({
				model: myModel,
				childrenAttr: "children"
			});
			dojo.body().appendChild(document.createElement("hr"));
			var txt = document.createElement("H1");
			txt.innerHTML="Dijit Forest Store Programmatic";
			dojo.body().appendChild(txt);
			dojo.body().appendChild(tree.domNode);
			dojo.body().appendChild(document.createElement("hr"));
			tree.startup();
		});
	


        

Dijit Forest Store declarative

alert("Execute of node " + continentStore.getLabel(item) +", population=" + continentStore.getValue(item, "population"));

According to me, this is due

According to me, this is due to a bug in the implementation of the mayHaveChildren function in TreeStoreModel.

The implementation now uses :
return dojo.some(this.childrenAttr, function(attr){
return this.store.hasAttribute(item, attr);
}, this);

but since this.childrenAttr is a string, it will be treated by dojo.some as an array of characters (see comment at dojo.some).
This function therefore checks whether the characters 'c','h','i','l','d','r','e','n' are an attribute of the item, which is most probably not the case.
It thus returns false (except in these very improbable cases).

The correct implementation should wrap this.childrenAttr into an Array:
return dojo.some([this.childrenAttr], function(attr){
return this.store.hasAttribute(item, attr);
}, this);

I created a bug for this: #6126.

hope this helps,

bartlebooth

Comparable issue in getChildren from dijit.tree.TreeStoreModel

With the modification from the previous post, the folders are now shown as expandable, but when you expand them, the children are not shown yet.

There seems to be another issue in the implementation of the method getChildren of dijit.tree.TreeStoreModel :

for (var i=0; i

childrenAttr

childrenAttr is an array (see the definition of TreeStoreModel and needs to be passed in as such (if you are overriding the value).

But I will change the parameter name to be childrenAttrs so it's less confusing. Plus fix the comment to say String{] rather than String.

childrenAttr as Array

Thanks for pointing this out.

But in the code from the samples above, as well as in the test test_Tree_Programmatic.html, childrenAttr is defined as a string.
The test should be adapted with high priority, as this is often the first source for expirements.

bartlebooth

ah!

Ah, you are right, I'll fix test_Tree_Programmatic.html and the examples above; thanks for pointing that out. But note that for the parser, an array of length 1 looks suspiciously like a plain string. This is an array:

<div dojoType="dijit.tree.ForestStoreModel childrenAttrs="foo, bar">

... and this is also an array even though it looks like a string:

<div dojoType="dijit.tree.ForestStoreModel childrenAttrs="foo">

Thanks Bill, it works well,

Thanks Bill and bartlebooth, it works well, with the last nightly build.

Why are those QueryOptions not working?

Hi!
I like this tree thing, great work. But when I try to do a "deep" query, those query options don't work.
Well, I tried implementing my own model and tree, but I totally failed.. Seems as if it's a quite complicated mechanism, accessing these data stores.
I would like to use the dojo tree to build a small file-explorer-like user interface - with a folder tree on the left and a content part on the right, which shows current folder's content. I planed using only one data model (that tree) and both parts give a filtered view on that model.
Any ideas, how to do this? Or anybody who already build such a dojo-file-explorer and would like to post it here? Would be a great demo :-)
Best regards
Daniel

don't understand

Sorry, I have no idea what you mean by QueryOptions or "deep" query. You'd have to inline a code sample.

code sample

Thanks for your quick response. To have an example, I took the tart data and added a sub category and an unspecified tart to the root node. Here it is:

{ label: 'name',
  identifier: 'name',
   items: [
     { name:'Fruit', type:'category'},
     { name:'Cinammon', type: 'category',
       children: [
		  { name:'Sub category', type: 'category',
		       children: [
		          { name:'Another Cinammon Roll', type:'poptart' },
		          { name:'Another Brown Sugar Cinnamon', type:'poptart' },
		          { name:'Another French Toast', type:'poptart' }
		       ]
		  },
          { name:'Cinammon Roll', type:'poptart' },
          { name:'Brown Sugar Cinnamon', type:'poptart' },
          { name:'French Toast', type:'poptart' }
       ]
     },
     { name:'Unspecified tart', type: 'poptart'},
     { name:'Chocolate', type: 'category'}
  ]
}

Let's imagine, I would like to build a tree that only contains categories - and no tarts. Without using any tree interfaces, I may fetch all categories using a query that searches "deep" in the data store (see also dojo/data/api/Read.js). The result is - as far as I see - a list or a tree of categories.

store.fetch({
	query: {type:"category"},
	queryOptions: {deep:true},
	onComplete: done,
	onItem: gotItem,
	onError: gotError
});

And with this result I was encouraged to test the queryOptions={deep:true} parameter on the dijit tree and the dijit tree model, too. But it doesn't exist, or does it?

The following code (without that parameter) filters my tart data only on the first level - so the "unspecified tart" doesn't show up in the tree because it isn't a category. Though the "Cinammon" category contains both: categories and tarts. And I want it to only show the sub category!

Any ideas? Or do I missunderstand something?

ah!

Ah, I had forgotten that there's a queryOptions parameter to store.fetch()... it's true, Tree doesn't have any code to process that; ie, it doesn't have a queryOptions parameter. So you would need to do something more complicated, namely to override ForestStoreModel's getChildren() method to do a deep query for the children of root, and to filter out poptarts from it's result set.

I'm actually working on something very similar...

I'm actually working on something very similar...
I'm not doing any 'deep' querying, but you can one of my public examples here...
http://public.dev.whmi.biz/rheaghen/places4.html

hope this helps!

New tree, old bugs

It seems as if the new tree nodes are wrapping again when the container is not wide enough :(

yup

Yup, that's why #1728 is still open.

But, milestone changed from 1.1 to 1.2.

2 weeks ago, more or less, this bug was fixed on the trunk.
We used it instead of the 1.0.2 that has the same issue.

Now that we upgraded to the latest from the trunk (1.1) it is back to be broken and now marked as a 1.2 milestone bug. I guess as part of the latest changed to the model this bug fix was rolled back.

I will try to get the specifics from the guy that did the change to fix this problem on the 1.0.2 based on the code from a few weeks ago where this bug was already fixed.

My mistake

It seems we created our own tree based on old tree object we had.

So I guess, we will have to do the same rather then use the 1.1 tree that will still have this bug.

I have same problem

I found my tree nodes wrapping also when container is not enough large.
Could you please let me know the way you created for your own tree?

DOM node for Expando Node or Folder Label

All,

I'm having trouble getting the DOM node when an expando node or folder label is clicked. Any suggestions? No problem on leaf nodes.

Thanks.

Upgrade stings

Having spent ages (I mean really ages) getting my tree working the way I want, It's now broken all to hell.

My bug bear is the level of documentation for this. It really is shockingly poor. While there is some documentation covering how to create elements programatically (with javascript), there is rarely ever documentation for using them declaritively with HTML. Some of us (many?, all?) use template languages to generate documents. Using template languages to generate Javascript is not a place anyone wants to go thanks.

OK general gripe done. Now my current problems with trees.

1. When my tree is first loaded all the nodes are expanded. Why? If I wanted all the nodes expanded I would have said so. Is there actually a way to tell the tree to collapse or expand all nodes at startup? Has the functionality changed? Is there any documentation for this? Am I even seeing the expected behaviour?

2. When I call setContent on the container for this element (As part of a reload of a div) The root of the tree is rendered but none of the nodes are. If I debug (using Firebug on FF) I see the data, but does my tree see it? Oh No. What is curious is that the first load of the page works OK, but when I reload the content pane my tree's got no nodes (how does it smell? terrible). Sorry for that. I haven't rewritten to use a model. So my page has a store and it has a tree with a store attribute. Is there some model created for me that has some life-cycle issues. Probably. Now I am going to have to dig in and find out I guess.

Some better documentation of the tags for widgets would be really useful. As would some better documentation of the exposed methods and extension points of the widgets.

I guess I am looking at a rewrite of my use of trees now, and with some pretty poor documentation too. I picked a bad week to give up sniffing glue.

a slightly modded version of

a slightly modded version of the below stuffs should keep your tree collapsed, however the tree seems to have been designed to 'remember' its previous state to keep the end user from pulling out there hair and screaming 'cant you F@#$ing remember where I left off!'. Or you might provide the user with a simple button to easilyclose all the nodes under the current selection (you'll have to track the current selection your self).

dojo.addOnload(function(){
   collapseAll();
})

function collapseAll(branches){
   if(branches === undefined){     
      if(myTree === null){
         myTree = dijit.byId('myTreesId');
      }
      branches = myTree.getChildren();
   }
   var branch;
   for(var i = 0; i<branches.length; i+=1){
      branch = branches[i];       
      collapseAll(branch.getChildren());
      myTree._collapseNode(branch);
   }
}

about your tree stump problem, you didnt provide an example, so I will guess that you don't have any children under the root node of the tree, or your using a beta version of the tree thats not really a good idea to use in a full enterprise slution, in which case I suggest using the stable public release.

I have a nice example here if you like

http://public.dev.whmi.biz/rheaghen/places4.html

(with your firebug console, you can test out that collapseAll() function here too)

Can't thank you enough.

Thats so cool. The collapse works a treat.

It's funny though. The same tree seems to have different behaviour between 1.0 and 1.1 and the fact that the tree does not reload and that it does not collapse on load, leads me to suspect that I actually have some abnormal behaviour with my tree. However I do not see any errors reported.

With regard to the 'stump' (like that expression), I am using the publicly available 1.1 release. I will take a look at the link you posted next and try again. Thanks so much for those. A great help.

Paul B.

spoke too soon

Ah, sorry. I had actually reverted to using 0.9x and of course that was all working. When I start using 1.1 again I still have problems.

The call getChildren does not work on the tree itself. I get...

TypeError: theTree.getChildren is not a function message=theTree.getChildren is not a function

Looks like I still have some work to do.

Cheers,
Paul B.

Something is really broken

I am still getting both the problems I initially posted about.

I can collapse the nodes programatically with this...

dijit.byId('theTree').rootNode.collapse();

Only I cannot do this during onLoad because the root has no children at that moment. If I put the same line in onClick and then click the mouse in the tree it collapses.

Begs two questions...
1. Why is the tree expanded in the first place? It has nothing to do with statefulness. It simply doesn't matter what I do with the persist attribute, or what state I leave the tree in. It is just fully expanded when its first displayed. It smacks of the parsing and rendering process stopping too early. I have noticed that when the tree is rendering it is open initially and then as the rendering ends the tree collapses (under 0.9x). However under 1.1 it just never seems to gets to that point.
2. is onLoad too early? Is there another method?

The second problem where I reload a content pane that contains a tree is, to my mind, a bug. It is just inexplicable that the tree behaves differently between an initial load and when I call setContent. The HTML that goes into the contentpane is identical.

These problems just make the tree unusable.

Some more info

I have some more information regarding the expanded state of my tree during load.

My tree looks like this...

Root
-----Leaf 1
-----Leaf 2

In the Tree.js the _load function has these lines

// load top level children
this._expandNode(rn);

If I comment out the second line my root node is not expanded, just the way I want it. So is there any way I can stop this line executing other than modifying the source?

With regard to the problem of setContent where the incoming HTML has a tree, well I have just given up. I have modified my code to use a write store. This does mean I now have a store on the client and on the server and of course I run the risk of these getting out of step. But I just cannot work out why the tree does not render properly when I use setContent.

remove cookie?

Hmm, you could try removing the cookie that stores which nodes are open.

About the write store, in theory the server and client should keep in sync somehow w/fancy comet technology, but I guess that's just theoretical right now; unfortunately no such store is implemented yet. Too bad.

=========
Bill Keese
Project Lead (aka BDFL) of Dijit

OK I get it now

Bill,

Thanks for taking the time to respond. I think the misunderstanding is mine. It seems the tree is predicated on the expectation that the root node is always exapnded when a tree is loaded. Now I can work around that, but only by modifying the tree source to not execute the expand of the root. There doesn't seem to be a lifecycle method I can hook a collapse into.

I do think an expand root attribute would be valuable though. Surely it cannot be that uncommon a usage to want a new tree to be fully collapsed.

Cheers,
Paul B.

declarative

Hi Paul, it's not a documentation problem, it's just that we don't have a way to declare tree data declaratively, see #5422.

As for setContent(), I'm not sure what you mean. It sounds like you are trying to do something that isn't supported, perhaps calling Tree.setContent()? That method doesn't even exist.

=========
Bill Keese
Project Lead (aka BDFL) of Dijit

Clarifications

OK fair enough.

Actually what I mean was documentation for creating trees, model and stores using HTML rather that javascript. I do not mean declaring the data with HTML, but using the div tag to define the store, model and tree.

Actually the test...

http://archive.dojotoolkit.org/nightly/dojotoolkit/dijit/tests/test_Tree...

demonstrates exactly how to do it.

Again clarifying what I mean about setContent. I have a tree contained within a contentPane. I am not trying to push content into a tree, I am trying to push some server generated HTML (that contains a tree) into a content pane using a ajax request and then doing setContent on the contentPane

The first time the contentPane is rendered the tree is loaded and rendered (although it is fully expanded, which I think is wrong). However when I reload the contentPane the root is rendered, but none of the child nodes are drawn. If I do a console.debug(theTree) using firebug in firefox I can drill down the DOM to the model and the nodes are there. They just aren't rendered.

ah

Ah, I see what you mean. For the setContent() thing I'm not sure what's going on there. It sounds like a bug, if you can make a test case then please file it.

=========
Bill Keese
Project Lead (aka BDFL) of Dijit

An example would help

Hi All and especially bill

hey please check out this post and i would appreciate if u could please provide an example for me.
have been stuck for a very very long time now :-(

http://dojotoolkit.org/forum/dijit-dijit-0-9/dijit-support/dijit-1-1-tre...

thanks

Dino

your link is broken, please

your link is broken, please edit/correct or repost it.