Building a Treeview component (Part I)

Damjan Namjesnik
Vuetify
Published in
10 min readApr 16, 2018

--

Like with any library, there comes a time when something more advanced needs to be built and a suitable component is not available out-of-the-box. This has happened to me recently with Vuetify — I needed a treeview component for a project and realized that there isn’t one included with it.

So I have two choices:

  • take a component from some other library; or
  • build it by myself

Taking a component from another library means that some discrepancies in styling and usage could occur eventually, and possibly increased application size because of additional scripts and styles which need to be included. Not to mention potential clashing of CSS between Vuetify and whatever 3rd party library I choose.

There is a related feature request on Github, and after chatting with John Leider I learned that the work hasn’t started on an official VTreeview componet yet — so I have one more reason to go by the latter path and contribute to Vuetify project.

As an inspiration, let’s take a component I developed for another project which has most of the functionality one would expect from a treeview:

Inspiration for our treeview component

We can see the structure, highlight currently selected item, have ability to collapse and expand a subtree, add or remove items, move the items up/down and drag’n’drop the whole subtrees to some other part of the tree.

Requirements

Let’s make a list out of this and some additional functionalities which can come handy such as checkboxes and others.

  1. Highlight current item under cursor, mark selected item and show additional commands
  2. Show lines for the tree structure
  3. Collapse and expand subtree
  4. Add item command
  5. Inline editing of an item
  6. Remove item command
  7. Moving items up/down —via command or drag’n’drop
  8. Drag’n’drop items and subtrees
  9. Define icon for item or use file/folder icons
  10. Dynamically loaded subtrees, show loading spinner
  11. Checkboxes for items and subtrees
  12. Status icons for each of the items (like the green checkmark for the root above)

Regarding the design, the Material Design specification does not include a treeview component so we will try to utilize the existing Vuetify components as much as possible to get the consistent look and feel.

Structuring the components

My preference when creating a component in Vue is to use single file components approach, and it is possible to create reusable treeview this way (the component from above was created this way).

However, this requires having Vue Loader in place. In order to not have this dependecy, optimise the component and get full control over the interaction details we will use render functions for developing our treeview.

When thinking about treeview, we will go back to how a tree data structure is defined in computer science literature. Here is an extract from Wikipedia article about defining tree:

Recursively, as a data type a tree is defined as a value (of some data type, possibly empty), together with a list of trees (possibly an empty list), the subtrees of its children

Terminology

Let’s write some of terminology used when talking about trees (source: Wikipedia article):

  • A node is a structure which may contain a value or condition, or represent a separate data structure (which could be a tree of its own). Each node in a tree has zero or more child nodes
  • The topmost node in a tree is called the Root node
  • Child is a node directly connected to another node when moving away from the Root.
  • Parent is the converse notion of a child.
  • Siblings are a group of nodes with the same parent.
  • Leaf is a node with no children

Recursive components

As we can see from above definitions, a node can have a value and some children, which are again nodes with possibly some children.

So we will need to build some recursive components… Luckily, Vue enables us to do it out-of-box and you can see an example in the documentation.

For our treeview component, we will have an outer element (we will name it <v-treeview>) which holds all of the options and the items we want visualised in tree. As an end-user, I will need to write something like this to use the component:

Planned v-treeview component usage

Initially we provide the following attributes to the component:

  • items: an object representing our tree (see example below).
  • v-model: used to hold the list of selected items when we want to use checkboxes instead of simple labels. Sometimes we will not need checkboxes, so this attribute must be optional

We will also need to provide some additional options to our component, but we will cover this later as the need arises.

Here is an example of the items object:

Simple tree structure

Treeview options

From such structure, how should our component know which field to use as a caption and which as a list of children? We could use some convention, but this could require some additional tranformations of data before passing it to our component. For example, if we use file and folder analogy, we would have fileList as a field holding the names of file in a folder instead of children.

So first thing to add as options is the names of the fields, and we define some reasonable defaults:

  • captionField: ‘name’ — which field to use to show caption, a string
  • childrenField: ‘children’ — which field to use for children (this will be an array containing a list of immediate child nodes)

Also, we mentioned that we will use checkboxes — for this we will not need an option, but simply check if value property has been provided to the component or if it’s set to null. If it’s not set, we will show only text.

You may be wondering why we mention only value, but not v-model. Remember from the documentation, that v-model is only syntactic sugar:

Vue.js documentation about v-model

Rendering nodes

To render the actual items (nodes of the tree) we will use the fact that we need to know only how to generate the root, and that all of the children are rendered in the same way. This means that <v-treeview> will display the caption of the node but also be responsible to recursively render all of it’s children — i.e. we will render <v-treeview> using <v-treeview> components.

If you haven’t encountered recursive components so far, don’t worry, everything will be clear later as we show how it works.

So now that we have some basic idea how things should work, let’s start implementing the component.

Contributing to Vuetify development

Idea is to contribute this component back to Vuetify, so we will start by forking the project. Official guides on how to do is already exist (see here and here), so I will not go into all of the details about the preparation, but dive straight into the development.

Creating a new component

Under src/components folder you can see already a number of existing Vuetify components. In order to follow the same structure, we will create a new folder for our new component and add files there We will also add a style file for it in appropriate place.

Initial additions

What we added:

  • src/components/VTreeview/ — folder to hold all relevant files
  • src/components/VTreeview/index.js — used for component registration; I simply looked at how other components are registered and changed the name where needed :)
  • src/components/VTreeview/VTreeview.js — container for the component; for now we just render a <div>Treeview</div> using the render function. The h parameter of the render function is actually the createElement function which Vue uses for element creation, like it’s name says :) We’ll come back to how rendering works later on.
Starting point for v-treeview component
  • src/stylus/components/_treeview.styl — here we will add any kind of specific styling our new component needs

Things we changed:

  • src/components/index.js — added one line for exporting the treeview component
Register v-treeview as one of Vuetify components

Implementation

Adding options (properties) to component

We mentioned earlier that component will have some options, so let’s add component properties to VTreeview.js:

Treeview option definition

Here we are defining the attributes which can be passed to our treeview component: we set a type and default value. Default values are important because later on we are relying on finding the correct fields in the provided items. If we set treeview to a wrong kind, we will get a warning from Vue type checking.

Now if we have fileList in our items instead of children, we can override the defaults and use component like this:

Overriding field name used for children elements

Rendering a simple tree

To render the tree, we will need some text to show. So let’s start first by adding a computed property to retrieve the caption of the item based on the given field:

Getting the caption of the item

Now we will adjust the rendering function to display the caption:

Rendering the caption

Now we have the root displayed. The next step is to render the children elements. Now comes recursion into the game — we will add all of the children as another set of treeview components after the caption. Let’s first add some references to the style file and to itself:

Self-referencing the treeview and adding the style

We referenced the VTreeview as it was any other component, and we can think of it like that — it’s just a component which displays some items like a tree, how it works we don’t really need to know :)

For recursion to work, it’s important that this self-referencing eventually stops. In our case, this is guaranteed because we have limited amount of children at all levels, and at some point there are no more child elements.

Let’s create a function which will generate one such child it’s given to as parameter:

Generate a single child treeview element

The createElement function is also available on the Vue instance as this.$createElement, so we can use it in our helper methods.

  • The first parameter it gets is a HTML tag name, component, or a function returning one of these. In our case we are giving it VTreeview component options we imported.
  • The second parameter is an object which corresponds to attributes we would give to the component. Here we added a class ml-4 (margin left of 4 space units) to push the child a bit to the right and for the props we are giving the child all the props of current component except the items — for items we will give the child items instead.
  • The last parameter is the children elements. Since we are not using it, we can safely omit it.

Now generating all child elements means to iterate over all of the children of the given item and call this function. Let’s make another helper function for this:

Generate all child elements

Notice how we are actually checking if the items has a childrenField — it is a good practice to expect gaps in your data and be ready for them. We actually did omit children for one of items in the items property given to <v-treeview> component.

Last helper function we do for now is to generate the root element instead of hard-coding the caption as text. Having all of these helper functions comes in handy later as we are adding new features.

Generating the root label

The genRoot function is pretty similar to our previous version of the render function — here we are generating a <label> element which has a class treeview-label (we will need it later) and the caption as the textual content.

Finally, let’s adjust the render function to use all of these helper functions. We will put all of the treeview in a <div> which has root and children underneath. Because genChildren function returns an array, we will use spread operator () to get individual elements out of it. The function now looks like this:

Rendering the root and the children

And the current look of our treeview component in action is not very nice, but clearly shows a structure:

First version of our treeview component in action

And if you open Vue Devtools, you can see how the actual components are nested one under another.

VTreeview as can be seen in Devtools

What’s next

Now that we have some basic structure for the treeview component, it is time to make it look nicer and have more features from our list.

In the Part II of our development journey, we will continue to add features to the VTreeview component. We will start by adding possibility to select elements, to get this kind of nicer looking component:

VTreeview with selection

--

--