The main idea is to have one component that will accept data property (with unknown data structure for now) and two other properties for summary an open; first used for defining the name used in the details tag and the displayed text, and the second used to see if the node tree is open or not.
I hope you will like it :)
Enjoy!
🟢 Building Recursive components in Vue
In this example, I am using VueUse onClickOutside utility function to handle outside click action (I just love this utility!).
First, let's define the props that this component will accept:
const props = defineProps({
summary: {
type: String,
default: 'disclosure'
},
open: {
type: Boolean,
default: false
},
content: {
type: Object,
default: () => ({})
}
})
Next, let's define the reactive ref and computed variables:
const computedOpenState = ref(props.open)
const details = ref(null)
const hasNestedChildren = computed(() => props.content.children?.length)
And finally, last in the script tag, let's add function to handle both toggle details and outside click:
const toggleDetails = (event: ToggleEvent) => {
computedOpenState.value = event.newState === 'open'
}
onClickOutside(details, () => (computedOpenState.value = false))
Now, let's add a bit of template to our component. First if the nested content does not have more nested children, we just want to display a paragraph with the name of the summary:
<p v-if="!hasNestedChildren">
{{ summary }}
</p>
In other case, we want to utilise the details HTML tag:
<p v-if="!hasNestedChildren">
{{ summary }}
</p>
<details
v-else
:open="computedOpenState"
:name="summary"
ref="details"
@toggle="toggleDetails"
>
...
</details>
Inside the details tag, we want to add following code to handle both displaying the summary and nested node tree:
<summary>
{{ summary }}
</summary>
<template v-if="hasNestedChildren">
<NodeTree
v-for="(child, key) in content.children"
:key
:summary="child.summary"
:content="child"
@click="toggleChildren"
/>
</template>
Below, you can see full code of this component:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onClickOutside } from '@vueuse/core'
const props = defineProps({
summary: {
type: String,
default: 'disclosure'
},
open: {
type: Boolean,
default: false
},
content: {
type: Object,
default: () => ({})
}
})
const computedOpenState = ref(props.open)
const details = ref(null)
const hasNestedChildren = computed(() => props.content.children?.length)
const toggleDetails = (event: ToggleEvent) => {
computedOpenState.value = event.newState === 'open'
}
onClickOutside(details, () => (computedOpenState.value = false))
</script>
<template>
<p v-if="!hasNestedChildren">
{{ summary }}
</p>
<details
v-else
:open="computedOpenState"
:name="summary"
ref="details"
@toggle="toggleDetails"
>
<summary>
{{ summary }}
</summary>
<template v-if="hasNestedChildren">
<NodeTree
v-for="(child, key) in content.children"
:key
:summary="child.summary"
:content="child"
@click="toggleChildren"
/>
</template>
</details>
</template>
Finally, the component can be used like following:
<template>
<NodeTree v-if="data" :content="data" :summary="data.summary" />
</template>
And that's it! You have fully working recursive node tree component :)
📖 Learn more
If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below:
Vue School Link
It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉
✅ Summary
Well done! You have just learned how to build a recursive Vue component.
Take care and see you next time!
And happy coding as always 🖥️