Tree

A simple tree view to display hierarchical data.

Basic usage

By default, the tree expects an object with the following structure as input:

[
    {
        label: 'Node 1',
        children: [
            {
                label: 'Node 1.1',
                children: [...]
            },
            {
                label: 'Node 1.2'
            }
        ]
    },
    {
        label: 'Node 2',
        children: [...]
    }
]

Here is a live example:

    As you see the data are passed using v-model, which means that the tree is reactive to external changes, in both directions. You can also pass the data by doing :data="content" if you don't the host to be updated.

    TIP

    There is an alternate notation you can use when you only need the labels, that uses an array:

    // Single level
    ['a', 'b', 'c']
    
    // To create a sub level, create sub arrays:
    ['a', ['b', 'b1', 'b2'], 'c']
    // The first item of the sub array is used as label, the reste are sub nodes.
    
    // It works with any number of levels:
    [
        'a',
        ['b', 'b1', ['b2', 'b21', ['b22', 'b211', 'b212'], 'b23']],
        ['c', 'c1', 'c2']
    ]
    

    If you want to inline the declaration in the template, you can do this:

    <bt-tree :data="['a', ['b', 'b1', 'b2'], ['c', 'c2', 'c3']]"></bt-tree>
    

    Which gives:

    The tree is highly customizable, as we'll see in the next sections.

    Nodes normalization

    No matter how the data are given to the tree, they always go through a normalization phase, that converts them into a Node object that is used internally.

    For each node, the component needs to figure out the following data from the input:

    • what's the label?
    • what's the identifier?
    • what are the children?
    • is it disabled?

    For each of these questions, the answer will depend on the input and the configuration.


    1) What's the label?

    • If the prop nodes-label is a function, it will always be called to get the label.
    • Else if the input is a , it is used as label.
    • Else if the input is an array, the first item of the array is used as label.
    • Else if the input is an object, the prop choices-label is expected to contain the name of the attribute to get the label from.
    <!-- Tell the tree that the "name" attribute should be used as label. -->
    <bt-tree nodes-label="name"></bt-tree>
    
    <!-- OR -->
    
    <!-- Tell the tree to execute a function for each node, that will return the label. -->
    <bt-tree :nodes-label="(o) => o.name" :data="[{name: 'A'}, {name: 'B'}]"></bt-tree>
    
    <!-- ALSO -->
    
    <!-- "nodes-label" can contain replaceable segments. -->
    <!-- Here "{fullName}" and "{adherentId}" will be replaced by the value in each object. -->
    <bt-tree nodes-label="{fullName} (n°{adherentId})" :data="[{fullName: 'John', adherentId: 123}, ...]"></bt-tree>
    

    2) What's the identifier?

    • If the prop nodes-identifier is a function, it will always be called to get the identifier.
    • Else if the input is a , it is used as identifier.
    • Otherwise, nodes-identifier is expected to contain the name of the attribute to get the identifier from.
    <!-- Tell the tree that the "id" attribute should be used as identifier. -->
    <bt-tree nodes-identifier="id""></bt-tree>
    
    <!-- OR -->
    
    <!-- Tell the tree to execute a function for each item, that will return the identifier. -->
    <bt-tree :nodes-identifier="(o) => o.id" :data="[{id: 1, name: 'A'}, {id: 2, name: 'B'}]"></bt-tree>
    

    WARNING

    The identifier must be unique for the whole tree.

    WARNING

    Also, the identifier is necessarily a

    (excluding undefined).


    3) What are the children?

    • If the prop nodes-children is a function, it will always be called to get the list of children.
    • Else if the input is an array, the items from the index 1 until the end will be treated as children.
    • Else, nodes-children is expected to contain the name of the attribute to get the children from.
    <!-- Tell the tree that the "sub" attribute should be used to get the child nodes. -->
    <bt-tree choices-children="sub" :data="[{name: 'A', sub: [{name: 'A child 1'}, ...]}]"></bt-tree>
    
    <!-- OR -->
    
    <!-- Tell the tree to execute a function for each item, that will return the children. -->
    <bt-tree :choices-label="(o) => o.children"></bt-tree>
    
    <!-- OR -->
    
    <!-- Here 'b1' and 'b2' will be added as children of 'b', because the item is an array. -->
    <bt-tree :data="['a', ['b', 'b1', 'b2'], 'c']"></bt-tree>
    

    4) Is it disabled?

    • If the prop nodes-disabled is a function, it will always be called to get a boolean.
    • Else if the prop nodes-disabled is a string AND if the input is an object, it is expected to be the name of the attribute that holds a boolean answering the question.
    • Otherwise, the item will not be disabled.
    <!-- The "disabled" attribute should be used to know if the choice is disabled. -->
    <bt-tree nodes-disabled="disabled" :data="[{name: 'A', disabled: true}, ...]"></bt-tree>
    
    <!-- OR -->
    
    <!-- Tell the tree to execute a function for each item, that will return a boolean. -->
    <bt-tree :nodes-disabled="(o) => !o.enabled" :data="[{name: 'A', enabled: false}, ...]"></bt-tree>
    

    Remote nodes

    The content of the tree can be loaded remotely:

      NOTE

      There are several other remote related props, for which you can find the detail here.

      The remote module is fully integrated with Models and Transformers, so you can for example use the endpoint of a model and have a tree of models at your disposal when rendering the tree:

      <!-- Will use the endpoint "list_all" defined on the model "Article" to do the request. -->
      <!-- The resulting response will contain a list of "Article" objects, used to render the tree. -->
      <bt-tree remote-endpoint="list_all" remote-model="Article" :nodes-label="title"></bt-tree>
      

      More on how remotes work here.

      Customize

      In addition of the theming customization provided by Vue Typescript, you can customize the look of your tree via the node slot which is called for each visible node.

      Look at the Slots section for more detail about the slots.

        Lazy loading

        If your tree is too big or hard to iterate to be fetched in one go, you can activate the lazy loading by giving a value to remote-node-url-param.

        When remote-node-url-param has a value, fetch requests are contextualized per node. This means that a new request will be done for each opened node, with remote-node-url-param set to the id of newly opened node.

        In the example below, the tree is generated procedurally, and extends to infinity. When you open a node, a random number of children is generated (from 0 to 6):

          WARNING

          To use this functionality, your nodes must be uniquely identified. You can define what attribute to use as identifier via the nodes-identifier prop. More info here.

          Props

          Method Description Default

          v-model
          Bidirectional binding controlling the local data to display in the tree. null
          Define how to resolve the node's identifier. See Nodes normalization for detail. null
          Define how to resolve the node's label. See Nodes normalization for detail. label
          Define how to resolve the node's children. See Nodes normalization for detail. children
          Define how to resolve the node's children. See Nodes normalization for detail. disabled
          Alias of the type of model that is used as nodes. null
          Url to call to get the nodes of the tree. null
          HTTP method to use to call the server. 'GET'
          Name of the Endpoint to use to load the nodes of the tree. If a model type is defined, the endpoint will first try to resolve for this specific model. null
          Url parameters to use when building the request. {}
          Custom headers to add to the request. {}

          Slots

          Name Description
          node

          Called for each visible node to render.

          TIP

          You can easily test if the node is the root by testing if it has a parent (node.parent). The root node will only render if the show-root prop is true.


          Props:

          • : the actual Node instance for the node.
          • : a function you can call to inverse the collapsed state of the node.
          • : a function you can call to show the children of the node.
          • : a function you can call to hide the children of the node.
          • : a function you can call to remove the node and its children from the tree.

          The Node instance

          The object created for each node of the tree. It is passed as prop to the slots.

          Click here to see its definition
          class Node {
              /**
               * The text visible to the user.
               */
              label: string;
          
              /**
               * The value used to check for equality with other items.
               */
              identifier: Primitive|undefined;
              
              /**
               * `true` if the item is non-selectable by the user.
               */
              disabled: boolean ;
              
              /**
               * If `false` the children of the item have not been fetched yet.
               */
              fetched: boolean;
          
              /**
               * Number of non-filtered children.
               * It has no link with `childrenVisible`.
               */
              childrenVisibleCount: number;
          
              /**
               * Status of the remote fetching of children.
               * 
               * Can be: 
               *   - idle (NodeRemoteFetchStatus.Idle),
               *   - pending (NodeRemoteFetchStatus.Pending),
               *   - failed (NodeRemoteFetchStatus.Failed).
               */
              remoteFetchStatus: NodeRemoteFetchStatus;
          
              /**
               * Contains the error message given by the remote module in case a remote fetch has failed.
               */
              remoteFetchError: string|null;
          
              /**
               * List of child nodes.
               */
              children: Node[];
          
              /**
               * The raw value the Node instance has been created from.
               */
              originalValue: AnyObject;
          }
          

          Theming

          Below the list of all CSS variables and selectors available to customize the appearance of the component. If you're not familiar on how the theming works, please refer to the VueThemes documentation.

          Variables

          Variable Default Comment
          itemFontFamily var(--bt-font-family-base) -
          itemFontSize var(--bt-font-size-sm) -
          itemFontWeight var(--bt-font-weight-normal) -
          itemTextColor var(--bt-text-color-base) -
          itemTextHoverColor var(--tag-text-color) -
          itemBackgroundColor transparent -
          itemBackgroundHoverColor var(--bt-color-gray-50) -
          itemIconColor var(--bt-text-color-light) -
          itemDisabledOpacity 0.5 -

          Selectors

          Name Comment Pseudo classes Attributes Combinable with
          root The root container.
          -
          -
          -
          item The individual container of each node.
          Any
          Any
          + Built-in:
          [disabled]
          [expanded]
          [empty]
          -
          itemsWrapper The container a single level of the tree.
          -
          -
          -
          itemTitle Title of a node.
          -
          -
          item
          itemAddon The icon that appear on the left of a node.
          -
          -
          item

          Examples:

          cssSelectors: {
              item: {
                  background: '...'
              },
              'item:hover': {
                  background: '...'
              },
              'item[expanded]': {
                  border: '...'
              },
              'item:hover itemTitle': {
                  color: '...'
              }
          }