public interface TreeDataBinding
TreeGrid
component is a visual
representation of a tree and requires
a Tree
or ResultTree
datatype passed via the data
attribute to
initialize the tree view. The Tree
datatype is used
when you want to provide all of
the tree nodes in one shot at initialization time. The ResultTree
datatype is used
when you want portions of the tree to be loaded on demand from the server.
Providing all data to the Tree at creation
The simplest mechanism by which to initialize a Tree is to simply provide all the data
up-front when the Tree itself is created. Depending on the format of your tree data, this
can be done by setting root
or data
. This functionality is provided
by the Tree
class.
For examples of this type of databinding, see the following SDK examples:
Loading Tree nodes on demand
In this mode, tree nodes are loaded on-demand the first time a user expands a folder. This
approach is necessary for large trees. This functionality is provided by the
ResultTree
class, which uses a DataSource
to load data from the server. Each
DataSource Record becomes a TreeNode
.
When the user expands a folder whose children have not yet been loaded
from the server (or you programmatically call openFolder() on such a node), the client
automatically sends a DSRequest
to the server to ask for all
immediate children of
that node.
If you have a dataset that is "parent-linked"
,
that is, every node has
a unique ID (the idField
) and also has
a property with the unique ID of it's
parent node (the parentIdField
)
the tree can load child nodes by simply sending
a DSRequest with appropriate Criteria
. Given a parent node
with ID "225" in a tree
where the parentIdField
is
called "parentId", the criteria would be:
{ parentId : 225 }The client is asking the server: "give me all nodes whose parentId is 225", which are the children of node 225.
If you have a DataSource that supports simple Criteria
like
the above, and your
records have nodes with ids and parentIds, this strategy can be used by just declaring the
tree relationship in your DataSource: the tree will automatically use your
primaryKey
field as the idField
. To declare the
parentIdField
, declare a foreignKey
field with the
name of the primaryKey field.
If you have a tree where there is no convenient unique ID, for example, you have mixed types of nodes (for example, departments and employees), use one of the following approaches:
Typically two or more properties can be combined into a String that serves as a unique ID. For example, if you are loading a mixed tree of "Departments" and "Users", each of which have unique numeric IDs, you could generate synthetic node IDs like "department:353" and "user:311". Your server-side code will then receive these synthetic node IDs when the tree loads children, and you can parse the IDs, look up the appropriate object and return its child nodes.
In the case of filesystems or XML documents, you can use the full path to the file or XML element as the unique ID.
sent to the server
If having all the properties of the parentNode would allow you to look up children, this approach may be more convenient than having to generate synthetic node IDs and parse them when looking up children.
For example, with a mixed-type tree, your server-side code may be able to quickly identify the type of the parent node be looking for specific properties, and then call methods to look up children for that type of node.
In this case there is no need to declare an idField or parentIdField.
ResultTree
s are created for you by the TreeGrid
when you set
dataSource
, but you can pass an
initial dataset to a databound TreeGrid by
setting initialData
.
If you do not provide initialData
, the first DSRequest you receive will be a
request for the nodes under root. The id of the root node of the tree is the value of the
rootValue
attribute on the parentIdField
of the Tree DataSource.
For examples of this type of databinding, see the following SDK examples:
Folders and load on demand
When using load on demand, the Tree cannot simply check whether a node has children to
determine whether it's a folder, and will assume all loaded nodes are folders. To avoid
this, you can add a boolean field to your DataSource called "isFolder" that indicates
whether a node is a folder or not. If you already have a boolean field that indicates
whether a node is a folder, you can instead set isFolderProperty
to the name of
that field via dataProperties
.
Multi-Level load on demand
The ResultTree's DSRequests ask for the immediate children of a node only (by specifying
parentId
in the criteria). Any nodes returned whose parentId
field
value is unset or matches this criterion will be added to the tree as immediate children of the
node. However you are also free to return multiple levels of children. This can be done by
simply returning a flat list of descendents with valid id's and parentId's, exactly as though
you were initializing a multi-level tree via data
.
Note that when receiving multiple levels of children, the ResultTree's assumption is that if any children are loaded for a parent, then that parent is considered fully loaded.
When loading children for a given parent node, the ResultTree calls
DataSource.fetchData()
on its DataSource.
For custom code that may need to reference
the parentNode or tree in some way, the parent node whose children are being loaded is
available on the dsRequest instance in the DataSource flow as dsRequest.parentNode, where it
can be inspected during DataSource.transformRequest()
.
For an example of this feature, see the following SDK example:
Paging large sets of children
If some nodes in your tree have a very large number of immediate children, you can enable
fetchMode:"paged"
to load
children in batches. This means that
whenever the children of a folder are loaded, the resultTree
will set
startRow
and endRow
when requesting children from
the DataSource. This includes the initial fetch of top-level nodes, which are children of
the implicit root node
.
As with all paged DSRequests, the server is free to ignore startRow/endRow and
simply return all children of the node. This allows the server to make on-the-fly
folder-by-folder choices as to whether to use paging or just return all children. However,
whenever the server returns only some children, the server must provide an accurate value for
totalRows
.
If the server does return a partial list of children, the resultTree
will
automatically request further children as they are accessed; typically this happens because
the user is scrolling around in a TreeGrid
which is
viewing the
resultTree
.
In this mode, the server may return multiple levels of the tree as described above ("Multi-Level load on demand"), however, by default the server is not allowed to return folders that are open, as this creates a potential performance issue: consider the case of a user scrolling rapidly into an unloaded area of the tree, skipping past many nodes that have not been loaded. If the skipped nodes might have been open parents, then the only way to know what nodes should be visible at the new scroll position is to load all skipped nodes and discover how many visible children they had.
If this performance consequence is acceptable, the restriction against returning open
folders from the server may be lifted on a tree-wide basis by setting the
canReturnOpenFolders
property to true
and/or on a folder-by-folder basis by setting the property named by the
canReturnOpenSubfoldersProperty
to
true
. In this case, it is recommended to also set
progressiveLoading
to
true
to prevent
users from causing a large number of nodes to be loaded by scrolling too far ahead in the
tree.
In addition, if any folder is returned already open, it must include children via the
childrenProperty
or there
will be an immediate, new fetch to
retrieve the children. When returning children, a partial list of children may be
returned, but if so, the childCountProperty
must be
set to the total number of children.
Paged ResultTrees may also be filtered like other trees (see
ResultTree.setCriteria()
).
However, if keepParentsOnFilter
is
enabled then server filtering is required. To illustrate with an example, consider a case
where the ResultTree has 10,000 folders at root level and where criteria applied to their
children would eliminate all but 20, which happen to be at the end of the 10,000. Purely
client-side logic would have to perform 10,000 fetch operations to check whether each
root-level node had children before arriving at the final set of 20.
For examples of this feature, see the following SDK example:
NOTE: trees with thousands of visible nodes are very difficult for end users to navigate. A majority of the time the best interface for showing a very large tree is to show a TreeGrid that displays just folders, adjacent to a ListGrid that shows items within those folders.
For example, the data in your email account can be thought of as an enormous tree of folders (Inbox, Sent, Drafts, Trash etc) with thousands of messages in each folder. However, none of the common email clients display email this way; all of them choose to show folders and messages separately, as this is clearly more usable.
Before starting on implementing paging within sets of children, carefully consider whether an interface like the above, or some entirely different interface, is actually a superior option. It is exceedingly rare that paging within sets of children is the best choice.