Как реализовать выпадающие списки, где значение следующего будет зависить от предыдущего?
Изучаю рекурсивные компоненты
<div class="container">
<h4>Vue.js Expandable Tree Menu<br/><small>(Recursive Components)</small></h4>
<div id="app">
<tree-menu
:nodes="tree.nodes"
:depth="0"
:label="tree.label"
></tree-menu>
</div>
</div>
<script type="text/x-template" id="tree-menu">
<div class="tree-menu">
<div class="label-wrapper" @click="toggleChildren">
<div :style="indent" :class="labelClasses">
<i v-if="nodes" class="fa" :class="iconClasses"></i>
{{ label }}
</div>
</div>
<tree-menu
v-if="showChildren"
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label"
:depth="depth + 1"
>
</tree-menu>
</div>
</script>
let tree = {
label: 'root',
nodes: [
{
label: 'item1',
nodes: [
{
label: 'item1.1'
},
{
label: 'item1.2',
nodes: [
{
label: 'item1.2.1'
}
]
}
]
},
{
label: 'item2'
}
]
}
Vue.component('tree-menu', {
template: '#tree-menu',
props: [ 'nodes', 'label', 'depth' ],
data() {
return {
showChildren: false
}
},
computed: {
iconClasses() {
return {
'fa-plus-square-o': !this.showChildren,
'fa-minus-square-o': this.showChildren
}
},
labelClasses() {
return { 'has-children': this.nodes }
},
indent() {
return { transform: `translate(${this.depth * 50}px)` }
}
},
methods: {
toggleChildren() {
this.showChildren = !this.showChildren;
}
}
});
new Vue({
el: '#app',
data: {
tree
}
})
Ссылка на кодпен.
Есть задача, сделать "хлебные крошки" из выпадающих списков. К примеру здесь это обычный селект, при выборе первого или второго элемента справа должен появиться еще выпадающий список с дочерними элементами, принадлежащими соответственно, элементу 1 или 2. И так до бесконечности. Как реализовать с использованием рекурсии? И без использования селекта? Также, слово выбор элемента появляется только при 1-ом выборе.
Очень долго сижу над этим и пока без продвижения.
Ответы (1 шт):
Автор решения: Vasiliy Rusin
→ Ссылка
App.vue
<template>
<div>
{{ selectedLoactions.map(({ label }) => label).join(", ") }}
<recursive-selection v-model="selectedLoactions" :options="locations" />
</div>
</template>
<script>
import RecursiveSelection from "@/components/RecursiveSelection.vue";
export default {
name: "App",
components: { RecursiveSelection },
data() {
return {
selectedLoactions: [],
};
},
computed: {
locations() {
return [
{
label: "Россия",
value: "Russia",
options: [
{
label: "Московская область",
value: "Moscow",
options: [
{
label: "Москва",
value: "Moscow",
},
{
label: "Красногорск",
value: "Krasnogorsk",
},
],
},
{
label: "Свердловская область",
value: "Sverdlovsk",
options: [
{
label: "Екатеринбург",
value: "Yekaterinburg",
},
],
},
],
},
{
label: "Япония",
value: "Japan",
options: [
{
label: "Префектура Токио",
value: "Tokyo",
options: [
{
label: "Токио",
value: "Tokyo",
},
],
},
],
},
];
},
},
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
RecursiveSelection.vue
Компонент рекурсивного прохода.
<template>
<div class="display-flex">
<reusable-select v-model="computedSelected" :options="options" />
<template v-if="nextLevelOptions">
<recursive-selection
v-model="computedValue"
:key="key"
:options="nextLevelOptions"
/>
</template>
</div>
</template>
<script>
import ReusableSelect from "@/components/ReusableSelect.vue";
export default {
name: "RecursiveSelection",
components: { ReusableSelect },
props: {
value: {
type: Array,
required: true,
},
options: {
type: Array,
required: true,
},
},
data() {
return {
position: null,
};
},
created() {
// При создании компонента запоминаем его позицию
// вложенности
this.position = this.computedValue.length;
},
computed: {
// Нам нужен ключ что бы обновлять вложенные объекты
key() {
return JSON.stringify(this.nextLevelOptions);
},
// Этот компьютед отвечает за изменение массива
// выбранных значений
computedValue: {
get() {
return this.value;
},
set(value) {
// Поднимаясь вверх по иерархии к значению текущего
// компонента добавляем значения вложенных
this.$emit("input", [this.computedSelected, ...value]);
},
},
// Этот компьютед отвечает за изменение селекта
computedSelected: {
get() {
return this.computedValue?.[this.position] ?? null;
},
set(value) {
this.$emit("input", [value]);
},
},
nextLevelOptions() {
return this.computedSelected?.options;
},
},
};
</script>
<style lang="scss">
.display-flex {
display: flex;
}
</style>
ReusableSelect.vue
Компонент селекта.
<template>
<select v-model="computedValue">
<slot>
<option :value="null" selected hidden disabled>Выберите из списка</option>
<option :value="option" :key="index" v-for="(option, index) in options">
<slot name="option" :option="option" :index="index">
{{ getLabel(option) }}
</slot>
</option>
</slot>
</select>
</template>
<script>
export default {
name: "ReusableSelect",
props: {
value: {
type: null,
required: true,
},
label: {
type: String,
default: "label",
},
options: {
type: Array,
required: true,
},
},
computed: {
computedValue: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
},
},
},
methods: {
// Это не обязательный метод, он нужен что бы мы
// могли использовать option chaining (?.) и
// nullish assignment (??) в шаблоне.
getLabel(option) {
return option?.[this.label] ?? option;
},
},
};
</script>
<style>
</style>