Skip to content
Martin edited this page Jun 14, 2015 · 41 revisions

About initializing Fancytree and implementing lazy loading.

There are several ways to define the actual tree data:

  • Format: plain Javascript object, JSON formatted string, or HTML DOM elements (<ul><li>)
  • Mode: synchronous or asynchronous
  • Trigger: immediate or lazy (on demand)

This information is passed using the source and lazyLoad options:

$("#tree").fancytree({
  // This option defines the initial tree node structure:
  source: ...,
  // This callback is triggered when a node with 'lazy' attribute expanded for
  // the first time:
  lazyLoad: ...,
  ...
};

The data format for source and lazyLoad is similar: a - possibly nested - list of node objects.

Passing data with the 'source' option

Pass a JavaScript array

i.e. an array of nested objects

$("#tree").fancytree({
  source: [
    {title: "Node 1", key: "1"},
    {title: "Folder 2", key: "2", folder: true, children: [
      {title: "Node 2.1", key: "3", myOwnAttr: "abc"},
      {title: "Node 2.2", key: "4"}
    ]}
  ],
  ...
};

The following attributes are available as 'node.PROPERTY': expanded, extraClasses, folder, hideCheckbox, key, lazy, selected, title, tooltip, unselectable.

All other fields are considered custom and will be added to the nodes data object as 'node.data.PROPERTY' (e.g. 'node.data.myOwnAttr').

Additional information:

Passing Tree Meta Data

It is possible to pass additional tree meta-data along with the list of children:

{
  foo: "bar",
  baz: 17,
  children: [
    {title: "Node 1", key: "1"},
    {title: "Folder 2", key: "2", folder: true, children: [
      {title: "Node 2.1", key: "3"},
      {title: "Node 2.2", key: "4"}
      ]
    }
  ]
}

The additional properties will be added to the trees data object:

alert(tree.data.foo); // -> 'bar'

Note: A top-level property named error is reserved to signal error conditions.

Load the data via ajax

source may be set to an jQuery.ajax() settings object:

  source: {
    url: "/getTreeData",
    cache: false
  },
  ...

The ajax service is expected to return valid JSON data:

[{"title": "Node 1", "key": "1"},
 {"title": "Folder 2", "key": "2", "folder": true, "children": [
    {"title": "Node 2.1", "key": "3"},
    {"title": "Node 2.2", "key": "4"}
  ]}
]

See also '[Howto] Handle custom data formats' below.

Read data from HTML markup

Note that this will be parsed and converted to the internal data format, so it is not the most efficient way to pass data. If Javascript is not available however, the UL markup will still be visible to the user.

$("#tree").fancytree();
<div id="tree">
  <ul id="treeData" style="display: none;">
    <li id="1">Node 1
    <li id="2" class="expanded folder">Folder 2
      <ul>
        <li id="3">Node 2.1
        <li id="4">Node 2.2
      </ul>
    <li id="k234" class="lazy folder">This is a lazy loading folder with key k234.</li>
  </ul>
</div>
  ...

The id attribute becomes the node.key (a unique key is generated if omitted). The title attribute becomes node.tooltip and is displayed on hover.

The following boolean node properties may be set using class attributes of the <li> tag: active, expanded, focus, folder, lazy, selected, unselectable. All other classes will be added to node.extraClasses, and thus become classes of the generated tree nodes.

Additional data can be added to node.data. ... using data attributes:

<ul>
  <li class="folder">jQuery links
    <ul>
      <li class="active" data-foo="bar" data-selected="true">jQuery home</li>
      <li data-json='{expanded: true, "like": 42}'>jQuery docs</li>

will create node.data.foo = "bar", node.data.like = 42. Note that special attributes will change the node status instead: one node will be selected, the other expanded.

A special syntax allows to set node.data.href and node.data.target while using a HTML markup that is functional even when JavaScript is not available:

<div id="tree">
  <ul>
    <li id="1"><a href="http://example.com" target="_blank">Go home</a></li>

will be parsed as node.data.href = "http://example.com", node.data.target = "_blank".

It is possible to pass additional tree meta-data with the container or outer <ul> element:

<div id="tree" data-foo="bar">
  <ul data-json='{"baz": "x", "like": 42}'>
    <li id="1">node 1</li>

The additional properties will be added to the trees data object: tree.data.foo = "bar" and tree.data.like = 42.

Use a deferred promise

source may be a jQuery deferred promise as returned by $.ajax() or $.getJSON():

$("#tree").fancytree({
  source: $.ajax({
    url: "/myWebService",
    dataType: "json"
  }),
  lazyLoad: function(event, data){
    data.result = $.getJSON("ajax-sub2.json");
  },
  [...]
});

Promises are also a general way to pass results that are asynchronuosly created. Have a look at the documentation.

Note: Also ECMAScript 6 Promises are accepted.

Using a Callback

source may be callback that returns one of the above data formats.

  source: function(){
    return [{title: "node1", ...}, ...];
  }

Lazy Loading

Single nodes may be marked 'lazy'. These nodes will generate ajax request when expanded for the first time. Lazy loading allows to present hierarchical structures of infinite size in an efficient way.

For example:

$("#tree").fancytree({
  // Initial node data that sets 'lazy' flag on some leaf nodes
  source: [
    {title: "Child 1", key: "1", lazy: true},
    {title: "Folder 2", key: "2", folder: true, lazy: true}
  ],
  // Called when a lazy node is expanded for the first time
  lazyLoad: function(event, data){
      var node = data.node;
      // Load child nodes via ajax GET /getTreeData?mode=children&parent=1234
      data.result = {
        url: "/getTreeData",
        data: {mode: "children", parent: node.key},
        cache: false
      };
  },
  [...]
});

The data format for lazy loading is similar to that of the source option.

If node.lazy is true and node.children is null or undefined, the lazyLoad event is triggered, when this node is expanded. Note that a handler can return an empty array ([]) in order to mark the node as 'no children available'. It then becomes a standard end-node which is no longer expandable.

Lazy Loading Sequence Diagram

Fancytree lazy loading

Recipes

[Howto] Handle data that is passed as 'd' property

See the enableAspx option to process ASPX WebMethod JSON object inside "d" property. http://flask.pocoo.org/docs/security/#json-security

[Howto] Handle custom data formats

The postProcess callback allows to modify node data before it is added to the tree. data.response contains a reference to original data as returned by the ajax request. It may be modified in-place:

postProcess: function(event, data) {
  // assuming the ajax response contains a list of child nodes:
  data.response[0].title += " - hello from postProcess";
}

It is also possible to create a new response object that will be used instead.

postProcess: function(event, data) {
  data.result = [{title: "Node created by postProcess"}];
}

A typical usecase would be to handle ajax return formats that wrap the node data in order to pass additional fault information. Example response:

{ "status": "ok",
  "result": [ (... list of child nodes...) ] 
  }

Example response in case of error:

{"status": "error",
 "faultMsg": "Bad luck :-/",
 "faultCode": 17,
 "faultDetails": "Sonething went wrong."
 }

could be handles like this:

postProcess: function(event, data) {
  var orgResponse = data.response;

  if( orgResponse.status === "ok" ) {
    data.result = orgResponse.result;
  } else {
    // Signal error condition to tree loader
    data.result = {
      error: "ERROR #" + orgResponse.faultCode + ": " + orgResponse.faultMsg
    }
  }
}

[Howto] Load data from a flat, parent-referencing list

This example converts a flat list of tasks into the nested structure that Fancytree expects. Every task has an id property and an optional parent property to define hierarchical relationship. Child order is defined by a position property and may differ from appearance in the flat list. (This example is taken from the Google Tasks API, see also issue 431.)

function convertData(childList) {
    var parent,
        nodeMap = {};
    
    if( childList.kind === "tasks#tasks" ) {
        childList = childList.items;
    }
    // Pass 1: store all tasks in reference map
    $.each(childList, function(i, c){
        nodeMap[c.id] = c;
    });
    // Pass 2: adjust fields and fix child structure
    childList = $.map(childList, function(c){
        // Rename 'key' to 'id'
        c.key = c.id;
        delete c.id;
        // Set checkbox for completed tasks
        c.selected = (c.status === "completed");
        // Check if c is a child node
        if( c.parent ) {
            // add c to `children` array of parent node
            parent = nodeMap[c.parent];
            if( parent.children ) {
                parent.children.push(c);
            } else {
                parent.children = [c];
            }
            return null;  // Remove c from childList
        }
        return c;  // Keep top-level nodes
    });
    // Pass 3: sort chldren by 'position'
    $.each(childList, function(i, c){
        if( c.children && c.children.length > 1 ) {
            c.children.sort(function(a, b){
                return ((a.position < b.position) ? -1 : ((a.position > b.position) ? 1 : 0));
            });
        }
    });
    return childList;
}

// Initialize Fancytree
$("#tree").fancytree({
    checkbox: true,
    source: {url: "get_tasks.json"},
    postProcess: function(event, data){
        data.result = convertData(data.response);
    }
});

[Howto] Load XML data

Also XML responses can be parsed and converted in the postProcess event. See this Stackoverflow answer and this demo for an example.

[Howto] Reload Tree Data

// Reload the tree from previous `source` option
tree.reload().done(function(){
  alert("reloaded");
});
// Optionally pass new `source`:
tree.reload({
  url: ...
}).done(function(){
  alert("reloaded");
});

[Howto] Recursively load lazy data

This can be achieved using standard functionality and this simple pattern:

loadChildren: function(event, data) {
    data.node.visit(function(subNode){
        if( subNode.isUndefined() && subNode.isExpanded() ) {
            subNode.load();
        }
    });
}

[Howto] Handle lazy loading in selectMode 3

In selectMode 3 (multi-hier) we can fix the the selection state of child nodes after a lazy parent was loaded:

$("#tree").fancytree({
  checkbox: true,
  selectMode: 3,
  source: { url: "/getTreeData", cache: false },
  lazyLoad: function(event, data) {
      data.result = {
          url: "/getTreeData",
          data: {mode: "children", parent: node.key},
          cache: false
      };
  },
  loadChildren: function(event, data) {
    // Apply parent's state to new child nodes:
    data.node.fixSelection3AfterClick();
  },
Clone this wiki locally