Introduction

This library allows you to present hierarchically organized data in a nice and logical manner based on the VueJS framework.
There are lots of libraries but there was always something from each (in my humble opinion). It is an attempt to make a perfect tree.

Just try it. The tree you were waiting for!

Features

Getting Started

Installation

It has to be installed to the VueJS instance. Please take a look at the official documentation to understand how to use VueJS components (if needed, of course).

You don’t need to care about styles, they are automatically appended to the document.

When used with a module system there are 3 ways to register the component (maybe more… I don’t know).
Okay. It’s our ways:

1
2
3
4
5
6
7
8
import Vue from 'Vue'
import LiquorTree from 'liquor-tree'

// global registration
Vue.use(LiquorTree)

// or
Vue.component(LiquorTree.name, LiquorTree)
1
2
3
4
5
6
7
8
9
10
import LiquorTree from 'liquor-tree'

// local registration
export default {
name: 'your-awesome-component',
components: {
[LiquorTree.name]: LiquorTree
},
...
}

To register the library you can choose between the 3 methods I mention above.

When used directly in browser you can include liquor-tree via CND (it is a latest version of the library):

1
<script src="https://cdn.jsdelivr.net/npm/liquor-tree/dist/liquor-tree.umd.js"></script>

Component Options

Name Type Default Description
multiple Boolean true Allows to select more than one node.
checkbox Boolean false checkbox mode. It shows checkboxes for every node
checkOnSelect Boolean false For checkbox mode only. Node will have checked state when user clicks either text or checkbox
autoCheckChildren Boolean true For checkbox mode only. Children will have the same checked state as their parent.
parentSelect Boolean false By clicking node which has children it expands node. i.e we have two ways to expand/collapse node: by clicking on arrow and on text
keyboardNavigation Boolean true Allows user to navigate tree using keyboard
propertyNames Object - This options allows the default tree’s structure to be redefined. See example
deletion Boolean | Function false If keyboardNavigation is false this property is ignored. This property defines deletion behaviour. See example
fetchData Object - See guide
dnd Object - See guide
editing Object - See guide

Structure

The component has only two props: data and options. For more information about props read VueJS documentation.
Additionally, see filtering.

Property data has its own structure for every node:

1
2
3
4
5
6
7
{
"id": Number,
"text": String,
"data": Object,
"children": Array,
"state": Object
}

By default a Node has the following states:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"selected": false,
"selectable": true,
"checked": false,
"expanded": false,
"disabled": false,
"visible": true,
"indeterminate": false,
"matched": false,
"editable": true,
"dragging": false,
"draggable": true,
"dropable": true
}

It is not necessary to pass all the states for every Node. It will automatically merged with the default states object.

Initial data example:

1
2
3
4
5
6
7
8
9
10
11
const treeData = [
{ text: 'Item 1', state: { visible: false } },
{ text: 'Item 2', data: { customProp: 'AAAAAAAAAAAAAAAAAAAA' } },
{ text: 'Item 3', state: { selected: true } },
{ text: 'Item 4' },
{ text: 'Item 5', children: [
{ text: 'Item 5.1', state: { disabled: true } },
{ text: 'Item 5.2', state: { selectable: false } }
]}
// and so on ...
]

Basic Usage

ES6 (using vue-loader)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div id="app">
<tree
:data="treeData"
/>
</div>
</template>

<script>
import Vue from 'Vue'
import LiquorTree from 'liquor-tree'

Vue.use(LiquorTree)

export default {
name: 'awesome-component',
data: () => ({
treeData: [
{ text: 'Item 1' },
{ text: 'Item 2' },
{ text: 'Item 3', state: { selected: true } },
{ text: 'Item 4' }
]
})
}

</script>

If you are using CDN that’s all you need to build your first app (I mean app that using VueTree library)

The following example illustrates linking to the VueTree library without having to register the library as a component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">
<tree
:data="treeData"
:options="treeOptions"
/>
</div>
</body>
<!-- first import Vue -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/liquor-tree/dist/liquor-tree.umd.js"></script>
<script>
new Vue({
el: '#app',
data: function() {
return {
treeData: [
// see above the format of data
],
treeOptions: {
miltiple: false
}
}
}
})
</script>
</html>

Guides

Basic Features

This example demonstrates default behaviour of tree without any configurations. Each node from received data has its own states properties (view full list)

You able to select multiple nodes with Ctrl key. The same behavior as we are used to ;)

Checkboxes

The example above is default mode. You can switch it to checkbox mode. To do it just add the tree’s option checkbox:

1
2
3
4
<tree
:data="treeData"
:options="{ checkbox: true }"
/>

States of node like checked and selected are not interchangeable. They can be used together.

Redefine Structure

This component has strict structure. But! You can easily redefine this format. Yeah… Sometimes you don’t want to change your server-side code and you have very different format for tree. To do this you just need to send the object:

For instance you have data:

1
2
3
4
5
6
7
8
[
{ 'SOME-AWESOME-PROPERTY-FOR-TEXT': 'Item 1' },
{ 'SOME-AWESOME-PROPERTY-FOR-TEXT': 'Item 2' },
{ 'SOME-AWESOME-PROPERTY-FOR-TEXT': 'Item 3', 'kids': [
{ 'SOME-AWESOME-PROPERTY-FOR-TEXT': 'Item 3.1' },
{ 'SOME-AWESOME-PROPERTY-FOR-TEXT': 'Item 3.2' }
]}
]

You just need to add propertyNames options to Component Options:

1
2
3
4
{
'text': 'SOME-AWESOME-PROPERTY-FOR-TEXT',
'children': 'kids'
}

Then your data will be transformed to a readable tree format. Awesome! See example below.

Keyboard Navigation

By default keyboardNavigation options is true. It allows user to navigate the tree using a keyboard. Navigation is implemented in the usual way (i.e Windows Navigation pane). Disabled Nodes are ignored.

You also have the abillity to define condition to remove Node. To do this, determine the deletion component option.
It receives Boolean object (default is false) or Function.

Ohh, too hard. See example:

1
2
3
4
<tree
:data="treeData"
:options="{ deletion: true }"
/>
1
2
3
4
<tree
:data="treeData"
:options="{ deletion: node => !node.hasChildren() }"
/>

The example above removes ONLY nodes that has checked state (use DEL code on your keyboard).

Filtering

We do not know where to show the field for filtering, how to stylize it and so on… It depends on a situation.
So we decided to provide a powerfull API to handle it (the library don’t know about other components on the page and this is not necessary). See examples to understand ;)

Default props:

1
2
3
4
5
6
7
8
{
emptyText: 'Nothing found!',
matcher(query, node) {
return new RegExp(query, 'i').test(node.text)
},
plainList: false,
showChildren: true
}

Async Data

There are two ways to set an async data:

minFetchDelay option - this option provides a minimum delay before rendering the children list. For example request takes 15 ms and you will not see the loading indicator. To see the indicator you need to set this property to 1000 (for example).

fetchData options:

1
2
3
4
5
6
7
{
treeOptions: {
minFetchDelay: 1000,
fetchData: `/assets/data/fetch0/data-{id}.json`,
// fetchData: `/data?id={id}&text={text}`
}
}
1
2
3
4
5
6
7
{
treeOptions: {
fetchData(node) {
return `/assets/data/fetch0/data-${node.id}.json`
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
treeOptions: {
fetchData(node) {
return fetch(`/assets/data/fetch0/data-${node.id}.json`)
.then(r => r.json())
.catch(e => console.log(e))
},

// or
fetchData(node) {
return axios.get('/user', {
params: {
ID: node.id
}
})
}
}
}

Inline Editing

By default, when editing, a text box appears with the value of the node.
When you press Esc, the changes are canceled. When you press Enter or click on any area of the page, the changes are applied.
Events: check below

Manual editing (calls node API)

Editing via options

Just add an option editing to the tree options.
You can add a state editable for node to prevent editing:

1
2
3
4
5
6
{
"text": "Not editable node",
"state": {
"editable": false
}
}

Integration with Vuex

The library is allow to pass store options. The tree is not update partially. It updates the whole tree items.
You must implement dispatcher to update the tree.
Working with modules is identical. Vuex forbids to have more than 1 getter with same name.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const Store = new Vuex.Store({
state: {
treeStore: []
},
mutations: {
updateTreeStore(state, newTree) {
state.treeStore = newTree
}
},
actions: {
updateTree(context, tree) {
context.commit('updateTreeStore', tree)
}
},
getters: {
tree(state) {
return state.treeStore
}
}
})

// -------

new Vue({
el: '#app',
store: Store,
data: () => ({
options: {
store: {
store: Store,
getter: () => {
return Store.getters.tree
},
dispatcher(tree) {
Store.dispatch('updateTree', tree)
}
},
checkbox: true
}
})
})

Drag & Drop

Now there is only basic functionality of DND includes events (dragging:start, dragging:finish). Just add dnd property to the tree options.
To more details see the Issue

Examples

JSON Viewer

I did this in 40 min … do not judge me strictly :)
In plans:

Custom Node

This example shows how to replace default content. It allows you to control content in any way. It is possible thanks to VueJS scoped slots.

Redefine Structure Example

For instance: we had a super-tree-component with its own structure (never mind, just for test). And we have a lot of dependencies to that component. This example shows how to apply data from server without a headache.

In this example we have structure:

1
2
3
[
MY_TEXT: 'some text', KIDS: []
]

A library doesn’t know this format. But we can add propertyNames options and redefine structure. See example

Custom Theme

This example shows how to use tree in real life

Accordion

The library doesn’t have a such property. It uses provided API.

API

Tree API

To have directly access to the Tree API you have to use ref property of component. See more Child-Component-Refs.

Tree.find(criteria, [multiple = false])

By default this method finds the first found node. You can add the second parameter and this function will return all found nodes.

Tree.findAll(criteria)

This method is “syntactic sugar” of Tree.find(criteria, true)

Tree.selected()

Tree.checked()

Tree.append(criteria, node)

Tree.prepend(criteria, node)

Tree.before(criteria, node)

Tree.after(criteria, node)

Tree.remove(criteria, multiple = false)

Selection API

This array-like object has all array methods (forEach, map and so on) because it inherits Array object. This collection has very similar behaviour with jQuery. All actions apply to all items in the collection. I’m going to show one example in more details and other methods have similar logic.

Selection.select(extendList)

1
2
3
4
5
6
7
8
9
10
11
12
// Let's find Nodes which text starts with 'Java' and it's not disabled
let selection = this.$refs.tree.findAll({ text: /^Java/, state: { disabled: false } })

// or you can use (the second parametes is true):
// let selection = this.$refs.tree.find({ text: /^Java/, state: { disabled: false } }, true)

// Here we want to select all nodes in our collection.
// For single mode (multiple: false) it will do for ALL nodes:
// - unselect node
// - select node
// For multiple mode it will select ALL nodes in the collection
selection.select(true)

I think I should not explain behaviour of all methods. I hope it clear how it works.

Methods list:

Node API

A Tree component consists of Nodes. Every Node is a VueJS component, which has a node property linked to the Node class.
It is desirable not to work with the VueJS Node component directly. You have an API that returns a Node (not VueJS compoenent).
The tree mapping is based on the node states. Here are list of default states:

1
2
3
4
5
6
7
8
9
const nodeStates = {
selected: false,
selectable: true,
checked: false,
expanded: false,
disabled: false,
visible: true,
indeterminate: false
}

I hope that every state speaks for itself.

To check state you can use:

Node.indeterminate() - If a Node has more than one checked Node it will return true. Otherwise it returns false

We have reverse state checks and this is done purely for convenience:

To change Node state:

For instance Node is checked. When you call Node.check() it will not check Node again and not call node.checked event. This condition applies to all of the above methods. You don’t need to:

1
2
3
4
5
...
if (!node.checked()) {
node.check()
}
...

We have only 1 method which receives a single parameter. It is a Node.select(extendList). It this case if tree option multiple is true it will be added to selectionNodes list and user will see more than one selected Nodes. Example:

1
2
3
4
5
// In our example we have one selected node. Using API we are able to find Node: 
let someCoolNode = this.$refs.tree.find('Awesome NODE')

// It will select found Node and we will have 2 selected Nodes
someCoolNode.select(true)

Adding, removing, finding Nodes

You probably know jQuery and how it works with DOM objects. It is very similar.

These methods add children or insert nodes before/after.

Argument node can be:

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// In this example we DO NOT using Selection API...
let myAwesomeNode = this.$refs.tree.find({state: { selected: true } })[0]

if (myAwesomeNode) {
myAwesomeNode.append('LAST Child of Awesome Node') // to the end of the list
myAwesomeNode.prepend('FIRST Child of Awesome Node') // to the start of the list

myAwesomeNode.before({
text: 'Heey!!',
state: {
checked: true
}
})
}

Node properties

Name Type Description
Node.parent { Object | null } Link to parent ** For root node it will be null
Node.text String Node text. It can be HTML
Node.depth Int Node depth. Depth for root nodes is 0
Node.tree Object Link to a Tree instance (not Vue component)
Node.vm Object Link to a VueJS component

Events

This example shows every possible event for the tree.