Using tree data

The built-in Store (and its subclass AjaxStore) both handle tree data. This guide shows the basics of working with such data.

Loading tree data

As with flat data, tree data can be loaded either inline or using Ajax (see other "Working with data" guides for more details on loading). Both methods by default expect data to contain child nodes in a children property on the parents:

store.data = [
    {
        id : 1,
        name : 'Proud parent',
        age : 38,
        children : [
            { id : 11, name : 'First born', age : 6 },
            { id : 12, name : 'Some kid', age : 3 }
        ]
    }
]

The name of this property can be changed, by assigning to Model.childrenField. See API docs for more information.

Records that are used in a tree are commonly also called nodes. There are two types of nodes: leaf nodes and parent nodes. A leaf node is a node which has no children. A parent node is a node which has children, or is going to have children when loading on demand is enabled.

To load a node as a leaf node, omit the children property, or set it to an empty array if convertEmptyParentToLeaf is true.

To load a node as a parent node, provide at least one task to the children property, or set it to an empty array or just true (to indicate load on demand) value if convertEmptyParentToLeaf is false.

In the case illustrated above, the store will have a rootNode property with one child node which itself has two children.

A node has the following extra properties by virtue of its position in the tree structure:

  • parent A reference to the parent node.
  • parentIndex The node's index in the parent's children array.
  • previousSibling A reference to the node's previous sibling if any.
  • nextSibling A reference to the node's next sibling if any.
  • childLevel The visible level of the node with 0 being the top level nodes.

Transforming flat data

Optionally a tree store can consume a flat dataset with nodes that have a parentId property. By configuring the store with tree : true and transformFlatData : true, the flat data is transformed into tree data:

const store = new Store({
  tree              : true,
  transformFlatData : true,
  data              : [
    { id : 1, name : 'ABBA' },
    { id : 2, name: 'Agnetha', parentId : 1 },
    { id : 3, name: 'Bjorn', parentId : 1 },
    { id : 4, name: 'Benny', parentId : 1 },
    { id : 5, name: 'Anni-Frid', parentId : 1 }
  ]
});

Saving row order

When data is loaded to the store, parentIndex is set automatically based on the nodes position. When task order is changed parentIndex is updated. Therefore to save row order, need to persist the field on the server, and when data is fetched to be loaded, need to sort it on the server by the field. This will put nodes in correct order and nodes will receive correct parent indexes.

Retrieving nodes

The "Using a Store" guide describes different ways of retrieving records. Currently only a couple of those are supported for trees:

// get node at any level by id
store.getById(12); // -> Some kid

// query for multiple nodes
store.query(node => node.age < 10); // -> [First born, Some kid]

Traversing nodes

The nodes in a tree can be traversed using Store#traverse():

store.traverse(node => {
    // code to run per node
});

CRUD operations

Adding nodes

Add child nodes by first retrieving the parent node and then calling its appendChild() method supplying a model data Object or a Model instance:

const newBaby = store.getById(1).appendChild({
    id : 13
    name : 'Baby',
    age : 0
});

Same goes for insertion using insertChild():

const parent = store.getById(1),
    firstBorn = parent.insertChild({
        id: 14,
        name : 'Actual first born',
        age : 18
    }, parent.children[0]);

console.log(parent.toJSON());

Removing nodes

Remove child nodes by first retrieving the parent node and then calling its removeChild() method supplying a Model instance, or call the node's remove() method which routes through to its parent's removeChild() method:

const parent = store.getById(1);
parent.removeChild(parent.children[1]); // Removes second kid
parent.removeChild(parent.children[0]); // Removes First born

console.log(parent.toJSON());

Modifying records

Works the same way as with flat data,

const newBaby = store.getById(13);
newBaby.name = 'Baby boy';

console.log(newBaby.toJSON());

See the "Using a store" guide.