Building a Treeview component (Part III)

Damjan Namjesnik
Vuetify
Published in
8 min readAug 28, 2018

--

In this article we continue describing development of a new Vuetify component for treeview. If you haven’t already, you can read Part I here and the Part II here.

This article has been long overdue in my task queue due to higher priorities with projects, so I will put focus on fulfilling at least some of the comments from the previous parts and enabling you to use the treeview component in your projects.

Please note, this component is a work in progress and there are some known issues and lack of features (as well as some unfinished ones). The current state as described below is used in some of my projects already, so I consider it ready but use at your own risk!

The version of Vuetify being used with this article series is 1.0.19. The below given code is not compatible with 1.1.x and newer versions at the moment, as this version introduced some behind-the-scene changes of how the components get rendered (see these release notes for more details).

Where we are

In the previous parts we built a foundation and demonstrated how to build a recursive treeview component with some nice features, which makes it pretty usable already:

Treeview features developed so far

Following your comments and questions about how to use the component already, we will start first by providing the source code and instructions on how to include it in your projects instead of focusing on adding new features.

As the current development version contains more features than what was described so far and some features have changed, additional examples will be provided about the usage.

Using the current development version of Treeview

As I am developing the treeview component, I am actually doing it by using it within a project. Fixing issues and adding features is then much faster as I have some real data which needs to be used in real scenarios.

Importing the component

So now forget for a moment the location of files mentioned within the Part I, if you can still remember them :)

We will start by creating a blank Vuetify project (see Vuetify Quick Start in documentation for details), but if you have an existing project, similar steps are applicable. Next step is to simply extract the files from the Github repository where I pushed the code of the current version.

The ZIP archive of the code can be downloaded from Github: Download ZIP.

The files can be extracted to the /src/components/vtreeview folder:

Three files are relevant for treeview component

Afterwards, import the Treeview component in the /src/plugins/vuetify.js and tell Vue to register it globally in the same way existing Vuetify components are:

Importing VTreeview component

This is it — now you have your own treeview component added to the project which you can use (and abuse :) as well as make any kind of quick changes you might need. For example, if you extracted files to a different sub-folder you will need to adjust some paths as well.

Examples of usage

Tree data source

Like in the first article, for a tree we need a structure in our state which holds the data. For our examples, we will use the following definition of nodes (shorter version shown, just to get the idea):

Tree definition (rest ommited for brevity)

You might notice some additional properties used here: id and icon. As the name says, id is an unique identifier and the icon is the icon which can be displayed in front of the label (you can use the same icon codes like for VIcon component, as it internally uses it).

Example 1: Simple tree

Let’s see how the first example looks and the markup used to generate it:

Basic variant is the simplest example — we could even omit caption-field and children-field properties there. The tree comes folded by default.

Basic variant
Basic variant markup

Often we would want to show expanded items by default:

Expanded tree
expand-all attribute is used to override default behaviour

Where is the root element, where are the icons? For my use case, ommiting root was a sensible default. If you want to show icons, you need to tell VTreeview which field holds the icon name by setting icon-field attribute.

Root and icons
To show root and icons use additional attributes

Example 2: One item can be selected

Before going further with the example, let’s get back to why we added the id property to the tree definition…

The id property for the node has been introduced for purpose of tracking the selected items. In previous part we were pushing the actual items into an array of selected objects internally, which works fine until the tree can dynamically change.

For example if you bind the tree definition to a computed property, we can ask what happens upon update of the property? The object references used to generate the individual tree nodes will be lost, and we will lose information about which items were selected. By using id’s, we can stop relying on having always the same objects and lookup actual object by it’s id.

Selecting a single node in the tree
Markup for enabling selection

As you can see, we need to bind the treeview to selectedItems, which is an array holding the selected node id. The array is used for this in order to enable same type of field even when multiple selection is allowed with checkboxes.

Attribute select tells the component to utilise the single-select mechanism.

Which field represents id is set with the keyField attribute (default is id, so it’s actually not needed in this example).

Example 3: Multiple selection with checkboxes

Multiple selection in treeview components is most often represented in my experience with checkboxes. So our implementation also follows this approach:

In basic variant, we don’t need much, just binding to selectedItems and the checkbox attribute:

Basic treeview with checkboxes (already expanded)
Markup for basic variant

However, we can combine previously described properties and have root and some icons as well:

Variant with root and icons
Same additional attributes used

Have you noticed that he last example only has [ 4 ] as contents of selected items, although multiple checkboxes are checked? The selection logic works in a way to minimize number of elements in selectedItems array and to use parent ids if all of the children are checked.

In case you need all of the children of a particular node, you could make a computed property which expands the list of ids with all of the child nodes. The VTreeview component doesn’t do it automatically for performance reasons (and also because I didn’t need this feature :).

Example 4: Dynamically loaded children

In this final example we will tackle a more complex case where the whole tree structure is not known in advance. For example, you might load parts of the tree dynamically from the backend. Another use case is if you have thousands of items which could be seen in a tree — current implementation renders all of the items so for performance reasons it would be advised to limit the visible contents only to those items which are actually being used.

How does dynamically loading child items look like? Let’s have a look at example:

Dynamically loading child element

First time we open an item which has dynamic children, a loading indicator is shown. After one or more items get loaded, closing and reopening the parent item does not trigger extra load.

The markup for this tree is simple… just a couple changes:

Markup for dynamic tree

Like before, be bind the items to dynamicItems, which in this example will be a computed property.

The loading-text is the text shown while the items are being loaded. Here we override the default “Loading, please wait…”.

The dynamicChildrenContext is bound to the variable of the same name. This object is passed to the function which is responsible for loading the items, and can be useful to transfer some extra information related to the local state.

The dynamicItems is a computed property which uses the existing items, but adds another child as the first one:

Making calculated tree items

The dynamicallyLoadedChild mentioned above is just a state variable initialized to null.

The important change here is setting the children to be a function called loadDynamicChild instead of an array. The VTreeview component will check the type of the children property, and if it stumbles upon a function, will switch to dynamic loading. This means, that the function gets executed, a loading indicator is displayed, the function is awaited on until it completes, and the loading indicator is hidden.

The loadDynamicChild function is a function which gets one argument passed onto — the dynamicChildrenContext property given as attribute to the component.

Let’s have a look how it’s implemented:

Loading logic implementation

We have a helper function called sleep which allows us to make a small delay to see the loading indicator.

The loadDynamicChild function is invoked after we click on arrow to expand the children. Here all of the logic for fetching data from API or any other long running operations should happen.

In our example, we are simply sleeping a bit and then setting the dynamicallyLoadedChild variable to an object.

Now Vue reactivity jumps automatically in the picture: because the dynamicItems is a computed property which depends on this variable, it gets re-calculated. If you jump back to the code, we have a check for existence of dynamicallyLoadedChild to make it into a children array. This prevents from setting children as function and removes dynamic logic from treeview. Of course, the dynamicItems object has changed and the VTreeview gets also updated to show the newly loaded child.

Download the code

Source code for all of the examples mentioned above can be downloaded in the Github repository: vuetify-treeview-example.

This is a project created according to Quick Start, cleaned up a bit, and with Vuetify dependency fixed to 1.0.19.

What’s next?

First step is to migrate the component to support the newer Vuetify versions, hopefully you will not have to wait for this so long :)

The idea is to have a treeview component in the Vuetify Frontend pack, so I will assist the core team in integrating this component with their work.

As for other useful features we mentioned earlier, we will see how fast it will go…

--

--