Building a Treeview component (Part III)
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:
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:
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:
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):
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.
Often we would want to show expanded items by default:
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.
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.
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:
However, we can combine previously described properties and have root and some icons as well:
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:
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:
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:
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:
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…