Skip to content
Martin Wendt edited this page Apr 20, 2019 · 8 revisions

Design goals and main concepts.

Fancytree is a refactored version of Dynatree. Read WhatsNew for details and migration hints.

Design Goals

  • Performant and efficient handling of big data structures.
  • Robust, consistent handling of parallel, asynchronous behavior.
  • Refactored to a more MVC-style design, so new features - like rendering as tree-grid becomes easier.
  • Allow to develop extensions as separate modules.
  • Compliance with the current jQuery.UI widget style guide.
  • Good test coverage.

Main Concepts

Typically, jQuery Plugins wrap around an existing HTML element, tweak the appearance and add behavior. Fancytree (as well as DynaTree) takes a different approach:

We have a tree data model as the backbone, i.e. an instance of the Fancytree class that contains a hierarchical structure of FancytreeNode objects.

A node may be active, selected, focused, and/or hovered. These states are independent, so one node can have all, some, or none of these states at the same time. See FAQ 'What statuses can a node have?'.

This structure is initialized on startup from a JavaScript data structure, an Ajax JSON response or a generator function. It is also possible to pass a reference to a <ul>/<li> element, which then will be parsed into the equivalent data model.

The tree's data model can be accessed and modified using an extensive object oriented API like tree.getNodeByKey() or node.setExpanded().

Most of these API methods will eventually call hook functions, which do the real work. Hook functions are members of the Fancytree class, with a name prefixed 'tree...' or 'node...', for example treeClear() or nodeRenderTitle().

Hook functions may be overloaded by extension modules, in order to implement new or customized functionality. Some of the standard functionality is implemented and delivered as extension modules, for example the table view, drag-and-drop, or persistence using cookies.

Some API functions are potentially asynchronous. For example node.setExpanded() on a lazy node may have to issue an Ajax request, wait for its response and then start the expand-animation which also takes some time. These functions generally return a $.promise, so handling deferred responses is easy:

node.setExpanded().done(function(){
  alert("expand animation has finished");
});

The HTML markup is rendered on demand. If a large tree with 10,000 nodes is partly collapsed, only the visible nodes will have DOM elements. Reducing the number of DOM elements allows to hold large data structures in the browser, but also has some implications. For example it might not be possible to access all nodes using a jQuery selector, because they simply don't exist: Use tree API functions instead. For the same reason it is not possible to bind events to all node elements directly. However this is rarely necessary, since Fancytree offers event handlers like click, dblclick, and keypress. Use event delegation otherwise.

Starting with v2.31 a tree grid may be set up to display in a viewport. A viewport defines a sub-range (start and count) of rows from the model that will be visible (expanded or not). This concept allows to store a huge data model (100k+ nodes) in the frontend, while only having a few <tr> elements materialized in the DOM.
As a side effect, scrolling needs to be implemented by shifting the start parameter, instead of relying on the browser. Also, the table header remains fixed (demo).

A tree may be set up for lazy loading: For one thing, the tree initialization may be delayed to an asynchronous Ajax request. This will result in a fast page load, and an empty tree displaying a spinner icon until the data arrives. Additionally, single child 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. But since neither all DOM elements nor even all node data is available, API functions like tree.getNodeByKey() or node.findAll() may not work as expected.