Wunderbaum

repository·main·Indexed 42 days ago

https://github.com/mar10/wunderbaum

A high-performance, zero-dependency JavaScript tree and treegrid control for web applications. Designed as the modern successor to Fancytree, it is framework-agnostic, written in TypeScript, and optimized for large data structures (100k+ nodes) using on-demand viewport rendering. Key features include drag and drop, editing, filtering, sorting, multi-selection, and lazy loading support. Currently in beta status (version 0.14.2-0).

Tokens
36.1K
Snippets
93
Records
134
Agent score
53%

What's inside Wunderbaum

  1. Overview of wunderbaum

    main

    wunderbaum is a modern, framework-agnostic tree and treegrid control for the web. It is the designated successor to Fancytree. It is designed to be performant with large data structures and provides an object-oriented API with zero dependencies.

    Key features include:

    • Drag and drop, editing, filtering, sorting, and multi-selection.
    • Full keyboard support.
    • Written in TypeScript and distributed as ES6 (ESM & UMD).
    • Framework agnostic (can be used with any JS framework or vanilla JS).
  2. Get started with Wunderbaum

    main

    Wunderbaum is a modern, framework-agnostic JavaScript tree/treegrid control designed for high performance with large data structures. It supports features like drag and drop, editing, filtering, sorting, multi-selection, and full keyboard support.

    Note: Wunderbaum is currently in beta status. Its API, markup, and stylesheets are subject to change.

  3. How Drag and Drop works in Wunderbaum

    main

    Wunderbaum implements drag and drop using the native HTML Drag and Drop API. This enables interaction between different tree instances, different browsers, and even native applications.

    Key Concept: Manual Tree Modification Crucially, Wunderbaum does not automatically modify the tree structure during a drag-and-drop operation. Instead, the tree fires a drop event, and the application developer is responsible for manually updating the tree (e.g., using node.moveTo() or node.addNode()) based on the event data.

  4. Understand the Wunderbaum data model and node states

    main

    Wunderbaum is built around a tree data model consisting of a Wunderbaum class instance containing a hierarchical structure of WunderbaumNode objects.

    Nodes can exist in several independent states simultaneously:

    • Active
    • Selected
    • Focused
    • Hovered

    Because these states are independent, a single node can be active, selected, and focused all at once, or none of these. The data model can be initialized from a JavaScript object, an Ajax JSON response, or a generator function. You interact with the tree using an object-oriented API (e.g., tree.getNodeByKey(), node.setTitle(), or node.setExpanded()).

  5. How viewport rendering and lazy rendering work

    main

    Wunderbaum uses a viewport concept: the rectangular, scrollable area on the page that contains the tree.

    To maintain high performance with large datasets (100k+ nodes), Wunderbaum uses on-demand rendering. Only nodes currently visible within the viewport are materialized as HTML elements in the DOM. Additionally, children of collapsed parent nodes do not have HTML elements.

    Important Developer Guidelines:

    • Do not manipulate HTML directly. Instead, modify the data model and call tree.update() or node.update() to reflect changes.
    • Event Binding: Because of lazy rendering, you cannot bind events directly to all node HTML elements. Instead, use Wunderbaum's built-in event handlers (like click, dblclick, and keypress) or use standard event delegation.
  6. Use dynamic options for nodes

    main

    Wunderbaum allows you to define certain node properties globally via a callback function. This enables dynamic behavior based on the specific node being processed.

    Supported dynamic options include:

    • checkbox
    • icon
    • iconTooltip
    • tooltip
    • unselectable

    When a callback is provided, it is executed for every node. The callback receives an event object e containing the node. If the callback returns nothing (undefined), Wunderbaum continues with its default processing.

    Example: Dynamically hiding checkboxes for nodes of type folder or creating custom tooltips.

    const tree = new mar10.Wunderbaum({
      checkbox: (e) => {
        // Hide checkboxes for folders
        return e.node.type === "folder" ? false : true;
      },
      tooltip: (e) => {
        // Create dynamic tooltips
        return `${e.node.title} (${e.node.key})`;
      },
      icon: (e) => {
        if( e.node.data.critical ) {
          return "foo-icon-class";
        }
        // Exit without returning a value: continue with default processing.
      },
      ...
    });
  7. Edit Cell Content in a Treegrid

    main

    Editing cells in columns other than the title column is not enabled by default. To implement cell editing, follow these two steps:

    1. Render Input Elements: Use the render(e) callback to inject HTML controls (like <input>, <select>, or <input type="checkbox">) into the cell. You can simplify this by defining an html property in your column configuration.
    2. Handle Changes: Implement the change(e) callback. This is triggered when the embedded input element fires a change event. Use e.util.getValueFromElem(e.inputElem, true) to retrieve the new value and update your node data.

    Helper Functions

    • util.setValueToElem(elem, value): Updates the embedded input element with the current node value.
    • util.getValueFromElem(elem, coerce): Reads the value from the input element. Setting coerce to true helps with type conversion (e.g., for dates).
    const tree = new Wunderbaum({
      columns: [
        { title: "Age", id: "age", html: "<input type=number min=0 tabindex='-1'>" },
        { title: "Status", id: "state", html: "<select tabindex='-1'><option value='h'>Happy</option></select>" }
      ],
      render: function (e) {
        const { node, util, renderColInfosById } = e;
        for (const col of Object.values(renderColInfosById)) {
          const val = node.data[col.id];
          util.setValueToElem(col.elem, val);
        }
      },
      change: function (e) {
        const { util, inputElem, info, node } = e;
        const colId = info.colId;
        node.data[colId] = util.getValueFromElem(inputElem, true);
      },
    });
  8. Format data using the Flat, Parent-Referencing List

    main

    The flat format is a highly compact way to represent the tree as a single list of nodes without nesting. This is useful for sending patches or minimizing payload size.

    Each node entry follows the structure: [PARENT_ID, [POSITIONAL_ARGS], {KEY_VALUE_ARGS}].

    • PARENT_ID: A string referencing a node.key or a 0-based numeric index of a previously appearing node. Use null for top-level nodes.
    • POSITIONAL_ARGS: Values provided in the order defined by the _positional array.
    • KEY_VALUE_ARGS: An optional object containing other properties.
    source: {
        "_format": "flat",
        "types": { "person": { "icon": "bi-person" }, "folder": { "icon": "bi-folder" } },
        "_keyMap": { "expanded": "e" },
        "_positional": ["title", "key", "type"],
        "_valueMap": {
            "type": ["folder", "person"]
        },
        "children": [
            [null, "Node 1", "id123", 0, { "e": true }],  // index=0, type=folder
            [0, "Node 1.1", "id234", 1],                  // index=1, type=person
            [0, "Node 1.2", "id345", 1, { "age": 32 }]  // index=2, type=person
        ]
    }
  9. Format data using the Object format

    main

    The most common format is a single object containing a children array. This format allows you to include metadata like types, columns, and _keyMap alongside the tree structure.

    {
      "types": {...},
      "columns": [...],
      "_keyMap": {...},
      "children": [
        {
          "title": "Books",
          "expanded": true,
          "children": [
            {
              "title": "Art of War",
              "author": "Sun Tzu"
            },
          ]
        },
        ...
      ]
    }
  10. How lazy loading works in Wunderbaum

    main

    Wunderbaum supports two patterns of lazy loading to handle hierarchical structures of potentially infinite size:

    1. Delayed Initialization: The entire tree initialization can be delayed until an asynchronous Ajax request completes. This shows a spinner icon until the data arrives, ensuring fast initial page loads.
    2. Lazy Child Nodes: Individual nodes can be marked as 'lazy'. These nodes trigger Ajax requests to fetch their children only when they are expanded for the first time.

    Caveats for Lazy Loading: Since the complete tree data model or all DOM elements might not be present in the browser, certain API functions like tree.getNodeByKey() or node.findAll() may not work as expected if the required nodes haven't been loaded yet.

  11. Understand the Wunderbaum node data model

    main

    When providing data via source, Wunderbaum maps JSON properties to a specific node model.

    Reserved Attributes: These properties are part of the core model and are accessed directly (e.g., node.title). They include:

    • checkbox, classes, expanded, icon, iconTooltip, key, lazy, radiogroup, refKey, selected, statusNodeType, title, tooltip, type, unselectable.

    Custom Data: Any property not in the reserved list is stored under the data namespace. You access these via node.data.propertyName (e.g., node.data.author).

    Important Notes:

    • title is the only mandatory property.
    • Node titles are automatically escaped to prevent XSS attacks.
    • The structure can be nested using a children list.