远-方的博客

dijit.Tree--dojo学习

在用户界面中的树可以表现很长的、分层次的结点列表,文件系统就

是一个经典的例子,如Windows的资源管理器,Dijit tree widget就

是这样的一个组件。

dojo创建树非常简单,即使是创建一棵复杂的树,你可以实现:
1、连接你的tree到任意dojo.data store,带或不带根item。
2、嵌套items任意深度,每一个分支都是独立展开的。
3、应用不同的图标到不同枝或叶。
4、安装一个global handler用于响应用户点击或双击一个特定的结点。
5、Tree会自动反射改变到data store(当使用TreeStoreModel 或 ForestStoreModel连接到data. store时)
6、允许结点通过dojo DnD API进行拖放。
7、在Tree上拖放,会直接更新data store。

 

Components Involved In A Tree

要理解如何使用Tree,你需要知道3种组件:

Tree

The Tree widget 自身仅仅是数据的视图. 它掌管着数据的显示及用户事件的控制.

The Tree 在直观的说就是一个黑盒子,开发者不必单独处理Tree的nodes. 对于node的一些事件如 onClick()等,实际上是 item 被点击了. Item 是Tree连接的 dojo.data store中的item.

说明:a Tree有一个当前被选Item的概念( currently selected item), such as the currently opened folder in a mail program.

Model

The tree model, 它表示了树中显示的数据的层次. Tree 可以与任何实施了model API的类交互, 但是典型的是使用 the TreeStoreModel or ForestStoreModel , 它们都有强大的dojo.data API Interface.

要重点说明的是 tree 仅仅是model上的一个 '''view'''. The model 完全负责与连接的data store的各种任务, 包括数据载入,数据改变. 它也负责着item的拖放操作.

换句话说,你不能直接“从Tree上删除数据”或者“把数据插入Tree", 但是你可以更新 model.

注意:Tree中的每一个item都必须有一个不同的identifier (the value of the identifier has to be unique).这与数据库中的主键概念是一样的。

Data Stores

虽然不是必需的,但一般情况下 the model 要和 a dojo.data store 连接的.

有许多种不同类型的stores, 如连接XML类型的store、连接JSON的store等,所有的stores,都有相同的API, 所以它们都能够通过 TreeStoreModel or ForestStoreModel来连接,根据它们有一个还是多个根结点来决定哪种Model。

 你可能会奇怪,为什么Tree不直接与 dojo.data store连接呢?有以下一些原因:

  • store中的items的父子关系不能被父item的children属性表示出来.对于关系型数据库,是使用子结点来指向其父结点的方法来表示的. The dijit.tree.Model 代码显示了是如何跟踪给定data store中的结点的父子关系的.
  • dojo.data载入children的接口是相当麻烦的,必须在children[]数组中的每一个item上调用l _loadItem() .
  • 有时候开发者可能使用自定的不连接data store的Model.

Relationship

这三者的关系最简单的表示如下:

Data Store --> Model --> Tree

在我们考虑拖放操作时会更复杂些,稍后会说到.

 

A Simple Tree Example

我们可以通过创建一个data store,一个model,一个Tree widget来在页面上显示一棵树.

A programmatic tree

Creating a programmatic tree is very simple:

<script type="text/javascript">
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.Tree");
dojo.addOnLoad(function(){
var store = new dojo.data.ItemFileReadStore({
url: "http://docs.dojocampus.org/moin_static163/js/dojo/trunk/dijit/tests/_data/countries.json"
});
var treeModel = new dijit.tree.ForestStoreModel({
store: store,
query: {"type": "continent"},
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
new dijit.Tree({
model: treeModel
}, "treeOne");
});
</script>

注意:TreeStoreModel/ForestStoreModel 的 childrenAttrs 参数是一个数组,所以它可以列出store里的多个属性.

<script type="text/javascript">
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.Tree");
</script>
<div dojoType="dojo.data.ItemFileReadStore" jsId="continentStore"
url="http://docs.dojocampus.org/moin_static163/js/dojo/trunk/dijit/tests/_data/countries.json"></div>
<div dojoType="dijit.tree.ForestStoreModel" jsId="continentModel"
store="continentStore" query="{type:'continent'}"
rootId="continentRoot" rootLabel="Continents" childrenAttrs="children"></div>
<div dojoType="dijit.Tree" id="mytree"
model="continentModel" openOnClick="true">
<script type="dojo/method" event="onClick" args="item">
alert("Execute of node " + continentStore.getLabel(item)
+", population=" + continentStore.getValue(item, "population"));
</script>
</div>

Icons

树中的每个node有一个图标. 和其他dijits一样,icon是用一个 CSS class 来表示出来的(which should load a background-image).

你可以通过覆盖dijit.Tree's getIconClass()函数来指定每个item的css class.

默认的 getIconClass() 方案显示两种类型的 icons: folders and leafs. (Actually, it has separate icons for opened and closed folders,

so that's three icons...) It tries to guess if the node is a folder or not by whether or not it has a children attribute:

1
        2
        3
      getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
        return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
        },
        

Note that the !item check refers to the root node in the tree, which may not have any associated item when using the old version of the Tree API,

connecting the Tree directly to a store instead of using a model.

That works fairly well, but will fail if mayHaveChildren() returns false for items with no children.

The definition of mayHaveChildren() for "empty folders" is actually somewhat vague, so it's best not to depend on it.

A better getIconClass() method for a Tree connected (through a model) to a dojox.data.FileStore would determine if the item was a folder

or not based on whether or not the item had the "directory" attribute (and it was set to true):

1
        2
        3
      getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
        return myStore.getValue(item, 'directory') ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf";
        },
        

If you want to have different icon types depending on the type of items in the tree (for example, separate icons for songs, movies, and TV shows),

then you really need to override the method to return a separate class name based on the type of item:

1
        2
        3
        4
        5
        6
        7
<script type="dojo/method" event="getIconClass" args="item, opened">
        if(item == this.model.root) {
        return (opened ? "customFolderOpenedIcon" : "customFolderClosedIcon");
        } else {
        return myStore.getValue(item, "type") + "Icon";
        }
        </script>
        

Hiding a Tree's root node

There's always a single root item for a Tree, returned by the model's getRoot() method.

It might be a real item from the store (such as a tree of employees, with the CEO as the root),

or it if there's no single root item in the store (like if the store lists continents but the top item,

"the world", is implied, the model is responsible for fabricating such a root item (from the perspective of the tree).

Correspondingly, all trees have a root node, corresponding to the root "item" from the model.

Sometimes you don't want that "the world" top level node to show up, especially if the Tree is inside a TitlePane/AccordionPane/etc.

with the label "The World". In that case you should set showRoot=false. The item still exists in the model but it's hidden on the screen:

<script type="text/javascript"> dojo.require("dojo.data.ItemFileReadStore"); dojo.require("dijit.Tree"); </script>

<div dojoType="dojo.data.ItemFileReadStore" jsId="continentStore"
url="http://docs.dojocampus.org/moin_static163/js/dojo/trunk/dijit/tests/_data/countries.json"></div>
<div dojoType="dijit.tree.ForestStoreModel" jsId="continentModel"
store="continentStore" query="{type:'continent'}"
rootId="continentRoot" rootLabel="Continents" childrenAttrs="children"></div>
<div dojoType="dijit.Tree" id="mytree2"
model="continentModel" showRoot="false">
</div>

Updating a Tree

People often ask:

  • how do I update a tree (adding or deleting items?

You can't update the tree directly, but rather you need to update the model. Usually the model is connected

to a data store and in that case you need to update the data store. Thus, you need to use a data store

that allows updates (through it's official API), like dojo.data.ItemFileWriteStore.

  • how do I refresh a Tree from the store?

This isn't supported. The store needs to notify the tree of any changes to the data.

Currently this is really only supported (out of the box) by dojo.data.ItemFileWriteStore,

as setting up a client-server dojo.data source where the server notifies the client whenever the data has changed

is quite complicated, and beyond the scope of dojo, which is a client-only solution.

Lazy Loading A Tree

People often ask how to lazy-load a tree, but this question is really unrelated to the Tree itself.

If you use a data store that is lazy loading, such as dojox.data.QueryReadStore or dojox.data.JsonRestStore,

then the data will be loaded lazily.

Drag And Drop

Tree's support drag and drop, meaning that a user can:

  • drop an item onto the tree
  • drag an item from the tree
  • move items within the tree

In the first and last case (ie, when an item is dropped onto the tree), the drop is processed by the model,

which in turn sends it to the data store (updating the underlying data). Thus:

  • the model must implement the pasteItem() method
  • the store must implement the dojo.data.api.Write interface

In addition, to enable DnD on the Tree you must dojo.require("dijit.tree.dndSource");

and the dndController="dijit.tree.dndSource" parameter must be specified to the tree

 

<script type="text/javascript">
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.tree.ForestStoreModel");
dojo.require("dijit.tree.dndSource");
dojo.require("dijit.Tree");
dojo.addOnLoad(function(){
var store = new dojo.data.ItemFileWriteStore({
url: "http://docs.dojocampus.org/moin_static163/js/dojo/trunk/dijit/tests/_data/countries.json"
});
var treeModel = new dijit.tree.ForestStoreModel({
store: store,
query: {"type": "continent"},
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
new dijit.Tree({
model: treeModel,
dndController: "dijit.tree.dndSource"
}, "treeThree");
});
</script>

You can also specify custom checkAcceptance() and checkItemAcceptance() to accept/reject items to the tree.

(The former function operates at the Tree level, and the latter operates per Tree node,

allowing things like rejecting dropping items onto leaf nodes.)

betweenThreshold

If between threshold is set to a positive integer value like 5 (which represents 5 pixels),

then when dragging within 5px of the top or bottom of a tree node,

it's interpreted as trying to make the drag source the previous or next sibling of the drop target,

rather than the child of the drop target. This is useful for when a user can control the order of the children of the child nodes:

 

<script type="text/javascript">
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.tree.ForestStoreModel");
dojo.require("dijit.tree.dndSource");
dojo.require("dijit.Tree");
</script>

Behind the scenes

What happens when a user moves an item from one position in a tree to another? It's actually quite complicated...

  1. The Tree widget does not change it's display at all. Rather, it notifies the model of the paste operation.
  2. The model updates the store.
  3. The store notifies the model that the data has been changed.
  4. The model notifies the tree of the change (presumably the children list of nodeA is one shorter, and the children list of nodeB has a new entry)
  5. The Tree updates it's display.

In this way, the Tree, Model, and data store are always in sync.

Context Menu

Tree has no built-in support for context menus, but you can use the Menu widget in conjunction with the Tree

<script>
    dojo.require("dijit.Menu");
    dojo.require("dijit.MenuItem");
    dojo.require("dijit.tree.ForestStoreModel");
    dojo.require("dojo.data.ItemFileReadStore");
    dojo.require("dijit.Tree");
</script>

Styling

Grid lines

If you don't want to display the grid lines for a Tree then simply write CSS rules to override the theme and hide the relevant background images.

The pertinent lines from tundra are:

.tundra .dijitTreeNode {
  background-image : url('images/i.gif');
  ...
}

/* left vertical line (grid) for all nodes */
.tundra .dijitTreeIsLast {
  background: url('images/i_half.gif') no-repeat;
  ...

.tundra .dijitTreeExpandoLeaf {
     background-image:url(images/treeExpand_leaf.gif);
}

Hover effect

Due to implementation details, the hover effect for tree nodes is done with a near-transparent image:

.tundra .dijitTreeNodeHover {
      /*background-color: #f6f9fa !important;*/
      /* using a transparent png so that we can still see grid lines, which are (unfortunately) behind the dijitRowNode that we are hovering over */
      background-image: url(images/treeHover.png);
      background-repeat: repeat;
      background-color: none !important;
}

So in order to change the hover effect you would need to create a new image (with for example 95% transparency), and write a CSS rule to override the one above.

You can also remove the hover effect altogether by just writing a CSS rule that sets background-image to none, overriding the above rule.

More examples

There are more extensive examples of using the tree

Accessibility

Keyboard

ActionKey
Navigate to first tree item*Tab
Navigate to the next siblingDown arrow
Navigate to the previous siblingUp arrow
Open a subtreeRight arrow
Close a subtreeLeft arrow
Navigate to open subtreeRight arrow
Navigate to parentLeft arrow
Activate a tree itemEnter
  • Note: The last tree item focused will be in the Tab order.

Known Issues

Using JAWS 10 in Firefox 3 the properties of each tree item are spoken including the open/close state and the level information. Using JAWS 10 with IE 8, the open/close state of each item is spoken but the level information is not spoken. In both Firefox 3 and IE 8 the JAWS user should be in App mode or virtual pc cursor off mode for best performance (toggle the mode via the insert+z key).

posted on 2009-12-03 22:48  远-方  阅读(3155)  评论(0编辑  收藏  举报

导航