DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Redefining DevOps: The Transformative Power of Containerization
  • 10 Traits That Separate the Best Devs From the Crowd
  • Exploring the Capabilities of eBPF

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Redefining DevOps: The Transformative Power of Containerization
  • 10 Traits That Separate the Best Devs From the Crowd
  • Exploring the Capabilities of eBPF
  1. DZone
  2. Data Engineering
  3. Data
  4. Build A Collapsible Tree Menu With Vue.js Recursive Components

Build A Collapsible Tree Menu With Vue.js Recursive Components

Ever been reading a comment section of a site and wonder how they make that collapsible, indented style view? Read on to find out!

Anthony Gore user avatar by
Anthony Gore
CORE ·
Updated Feb. 10, 20 · Tutorial
Like (8)
Save
Tweet
Share
18.30K Views

Join the DZone community and get the full member experience.

Join For Free

A recursive component in Vue.js is one which invokes itself e.g.:

Vue.component('recursive-component', {
  template: `<!--Invoking myself!-->
             <recursive-component></recursive-component>`
});

Recursive components are useful for displaying comments on a blog, nested menus, or basically anything where the parent and child are the same, albeit with different content. For example:

recursive_components_01.png

To give you a demonstration of how to use recursive components effectively, I'll go through the steps of building an expandable/contractable tree menu.

Data Structure

A tree of recursive UI components will be the visual representation of some recursive data structure. In this tutorial, we'll use a tree structure where each node is an object with:

  1. A label property.
  2. If it has children, a nodes property, which is an array of one or more nodes.

Like all tree structures, it must have a single root node but can be infinitely deep.

let tree = {
  label: 'root',
  nodes: [
    {
      label: 'item1',
      nodes: [
        {
          label: 'item1.1'
        },
        {
          label: 'item1.2',
          nodes: [
            {
              label: 'item1.2.1'
            }
          ]
        }
      ]
    }, 
    {
      label: 'item2'  
    }
  ]
}

Recursive Component

Let's make a recursive component to display our data structure called TreeMenu. All it does is display the current node's label and invokes itself to display any children.

TreeMenu.vue

<template>
  <div class="tree-menu">
    <div>{{ label }}</div>
    <tree-menu 
      v-for="node in nodes" 
      :nodes="node.nodes" 
      :label="node.label"
    >
    </tree-menu>
  </div>
</template>
<script>
  export default { 
    props: [ 'label', 'nodes' ],
    name: 'tree-menu'
  }
</script>

If you're using a component recursively you must either register it globally with Vue.component, or, give it a name property. Otherwise, any children of the component will not be able to resolve further invocations and you'll get an undefined component error.

Base Case

As with any recursive function, you need a base case to end recursion, otherwise rendering will continue indefinitely and you'll end up with a stack overflow.

In our tree menu, we want to stop the recursion whenever we reach a node that has no children. You could do this with a v-if, but our v-for will implicitly do it for us; if the nodes array is undefined no further tree-menu components will be invoked.

<template>
  <div class="tree-menu">
    ...
    <!--If `nodes` is undefined this will not render-->
    <tree-menu v-for="node in nodes"></tree-menu>
</template>

Usage

How do we now use this component? To begin with, we declare a Vue instance which has the data structure as a data property and registers the TreeMenu component.

app.js

import TreeMenu from './TreeMenu.vue'

let tree = {
  ...
}

new Vue({
  el: '#app',
  data: {
    tree
  },
  components: {
    TreeMenu
  }
})

Remember that our data structure has a single root node. To begin the recursion we invoke the TreeMenu component in our main template, using the rootnodesproperties for the props:

index.html

<div id="app">
  <tree-menu :label="tree.label" :nodes="tree.nodes"></tree-menu>
</div>

Here's how it looks so far:

recursive_components_02.png

Indentation

It'd be nice to visually identify the "depth" of a child component so the user gets a sense of the structure of the data from the UI. Let's increasingly indent each tier of children to achieve this.

recursive_components_03.png

This is implemented by adding a depth prop to TreeMenu. We'll use this value to dynamically bind inline style with a transform: translate CSS rule to each node's label, thus creating the indentation.

<template>
  <div class="tree-menu">
    <div :style="indent">{{ label }}</div>
    <tree-menu 
      v-for="node in nodes" 
      :nodes="node.nodes" 
      :label="node.label"
      :depth="depth + 1"
    >
    </tree-menu>
  </div>
</template>
<script>
  export default { 
    props: [ 'label', 'nodes', 'depth' ],
    name: 'tree-menu',
    computed: {
      indent() {
        return { transform: `translate(${this.depth * 50}px)` }
      }
    }
  }
</script>

The depth prop will start at zero in the main template. In the component template above you can see that this value will be incremented each time it is passed to any child nodes.

<div id="app">
  <tree-menu 
    :label="tree.label" 
    :nodes="tree.nodes"
    :depth="0"
  ></tree-menu>
</div>

Remember to v-bind the depth value to ensure it's a JavaScript number rather than a string.

Expansion/Contraction

Since recursive data structures can be large, a good UI trick for displaying them is to hide all but the root node so the user can expand/contract nodes as needed.

To do this, we'll add a local state property showChildren. If false, child nodes will not be rendered. This value should be toggled by clicking the node, so we'll need a click event listener method toggleChildren to manage this.

<template>
  <div class="tree-menu">
    <div :style="indent" @click="toggleChildren">{{ label }}</div>
    <tree-menu 
      v-if="showChildren"
      v-for="node in nodes" 
      :nodes="node.nodes" 
      :label="node.label"
      :depth="depth + 1"
    >
    </tree-menu>
  </div>
</template>
<script>
  export default { 
    props: [ 'label', 'nodes', 'depth' ],
    data() {
      return { showChildren: false }
    },
    name: 'tree-menu',
    computed: {
      indent() {
        return { transform: `translate(${this.depth * 50}px)` }
      }
    },
    methods: {
      toggleChildren() {
        this.showChildren = !this.showChildren;
      }
    }
  }
</script>

Wrap Up

With that, we've got a working tree menu. As a nice finishing touch, you can add a plus/minus icon to make the UI even more obvious. I did this with Font Awesome and a computed property based on showChildren.

Inspect the CodePen to see how I implemented it.

recursive_components_04.png

Tree (data structure) Data structure Vue.js Build (game engine)

Published at DZone with permission of Anthony Gore, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Redefining DevOps: The Transformative Power of Containerization
  • 10 Traits That Separate the Best Devs From the Crowd
  • Exploring the Capabilities of eBPF

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: