Reputation: 13172
My vue component like this :
Vue.component('list-category', {
template: "#lc",
props: ['data', 'category', 'search'],
data() {
return {
open: false,
categoryId: this.category
}
},
mounted() {
let isDataOpen = (d) => d.id === this.categoryId || d.children && d.children.some(isDataOpen);
this.open = isDataOpen(this.data);
},
computed: {
icon() {
return {
'fa-plus': !this.open,
'fa-minus': this.open,
}
},
isFolder() {
return this.data.children && this.data.children.length
},
isShow() {
return this.open ? 'show' : 'hide'
}
},
methods: {
toggle() {
this.open = !this.open
},
filterByCategory(id) {
this.categoryId = id
}
}
})
new Vue({
el: '#app',
data() {
return {
categories: [{
id: 1,
name: 'England',
children: [{
id: 3,
name: 'Chelsea',
children: [{
id: 7,
name: 'Hazard'
},
{
id: 8,
name: 'Morata'
}
]
},
{
id: 4,
name: 'Manchester United',
children: [{
id: 9,
name: 'Pogba'
},
{
id: 10,
name: 'Lukaku'
}
]
}
]
},
{
id: 2,
name: 'Spain',
children: [{
id: 5,
name: 'Real Madrid',
children: [{
id: 11,
name: 'Ronaldo'
},
{
id: 12,
name: 'Bale'
}
]
},
{
id: 6,
name: 'Barcelona',
children: [{
id: 13,
name: 'Messi'
},
{
id: 14,
name: 'Suarez'
}
]
},
]
}
],
category: 7
}
}
})
.active {
background: yellow;
}
.pd-search-filter > .panel-body ul.filter-category {
padding-left: 0;
list-style: none;
margin: 0 -15px 0;
}
.pd-search-filter > .panel-body ul.filter-category > li a {
display: block;
padding: 10px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pd-search-filter > .panel-body ul.filter-category > li a:last-child {
padding-left: 45px;
}
.pd-search-filter > .panel-body ul.filter-category > li a:focus, .pd-search-filter > .panel-body ul.filter-category > li a:hover {
background-color: #eeeeee;
text-decoration: none;
}
.pd-search-filter > .panel-body ul.filter-category > li a + ul {
padding-left: 0;
list-style: none;
}
.pd-search-filter > .panel-body ul.filter-category > li a + ul > li > a {
padding-left: 30px;
}
.show {
display: block !important;
}
.hide {
display: none !important;
}
<script src="https://unpkg.com/vue"></script>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<div id="app">
<div class="panel panel-default pd-search-filter">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-circle-o"></i> By Category</h3>
</div>
<div class="panel-body">
<ul class="filter-category" v-for="list in categories">
<list-category :data="list" :category="category"></list-category>
</ul>
</div>
</div>
</div>
<template id="lc">
<li>
<!--parent-->
<a v-if="isFolder" href="javascript:" @click="toggle">
<span class="fa fa-fw" :class="icon"></span> {{data.name}}
</a>
<!--if not folding, we do not need an binding event-->
<a v-else href="javascript:" :title="data.name" :class="{active: data.id === categoryId}" @click="filterByCategory(data.id)"><span class="fa fa-fw fa-circle-o"></span> {{data.name}}</a>
<!--children-->
<ul v-if="isFolder" :class="isShow">
<list-category v-for="(data, index) in data.children" :key="index" :data="data" :search="search" :category="categoryId"></list-category>
</ul>
</li>
</template>
Seems you need to see demo and full code
It's like this : http://jsfiddle.net/vxLhbo5m/861/
From demo seen category hazard active. If I click on morata category, it is not active. Whereas I have made the code
How can I solve this problem?
===========================================================================
Upvotes: 0
Views: 3466
Reputation: 135762
You would have to move the category calculator to a watcher (instead of mount()
) and emit/listen to some events from child to parent to update the category and collapse the non-selected sub-tree.
Changes:
Template:
Parent:
From:
<div id="app">
...
<list-category :data="list" :category="category"></list-category>
Adding listening to the category
event and updating the category
property at parent:
<div id="app">
...
<list-category :data="list" :category="category" @category="category = $event"></list-category>
Child:
From:
<template id="lc">
...
<list-category v-for="(data, index) in data.children" :key="index" :data="data" :search="search" :category="categoryId"></list-category>
Listen to the category
event and emit it up to the parent:
<template id="lc">
...
<list-category v-for="(data, index) in data.children" :key="index" :data="data" :search="search" :category="categoryId" @category="$emit('category', $event)"></list-category>
JavaScript (all in child):
Change filterByCategory
to emit event instead of mutating property:
From:
filterByCategory(id) {
this.categoryId = id
}
To:
filterByCategory(id) {
this.$emit('category', id);
}
Remove mounted
hook and add watcher:
Remove mounted:
mounted() {
let isDataOpen = (d) => d.id === this.categoryId || d.children && d.children.some(isDataOpen);
this.open = isDataOpen(this.data);
},
Add watcher to pick up when category
changes in the parent:
watch: {
category: {
handler() {
this.categoryId = this.category
let isDataOpen = (d) => d.id === this.categoryId || d.children && d.children.some(isDataOpen);
this.open = isDataOpen(this.data);
},
immediate: true
}
}
Demo:
Vue.component('list-category', {
template: "#lc",
props: ['data', 'category', 'search'],
data() {
return {
open: false,
categoryId: this.category
}
},
computed: {
icon() {
return {
'fa-plus': !this.open,
'fa-minus': this.open,
}
},
isFolder() {
return this.data.children && this.data.children.length
},
isShow() {
return this.open ? 'show' : 'hide'
}
},
methods: {
toggle() {
this.open = !this.open
},
filterByCategory(id) {
this.$emit('category', id);
}
},
watch: {
category: {
handler() {
this.categoryId = this.category
let isDataOpen = (d) => d.id === this.categoryId || d.children && d.children.some(isDataOpen);
this.open = isDataOpen(this.data);
},
immediate: true
}
}
})
new Vue({
el: '#app',
data() {
return {
categories: [{
id: 1,
name: 'England',
children: [{
id: 3,
name: 'Chelsea',
children: [{
id: 7,
name: 'Hazard'
},
{
id: 8,
name: 'Morata'
}
]
},
{
id: 4,
name: 'Manchester United',
children: [{
id: 9,
name: 'Pogba'
},
{
id: 10,
name: 'Lukaku'
}
]
}
]
},
{
id: 2,
name: 'Spain',
children: [{
id: 5,
name: 'Real Madrid',
children: [{
id: 11,
name: 'Ronaldo'
},
{
id: 12,
name: 'Bale'
}
]
},
{
id: 6,
name: 'Barcelona',
children: [{
id: 13,
name: 'Messi'
},
{
id: 14,
name: 'Suarez'
}
]
},
]
}
],
category: 7
}
}
})
.active {
background: yellow;
}
.pd-search-filter > .panel-body ul.filter-category {
padding-left: 0;
list-style: none;
margin: 0 -15px 0;
}
.pd-search-filter > .panel-body ul.filter-category > li a {
display: block;
padding: 10px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pd-search-filter > .panel-body ul.filter-category > li a:last-child {
padding-left: 45px;
}
.pd-search-filter > .panel-body ul.filter-category > li a:focus, .pd-search-filter > .panel-body ul.filter-category > li a:hover {
background-color: #eeeeee;
text-decoration: none;
}
.pd-search-filter > .panel-body ul.filter-category > li a + ul {
padding-left: 0;
list-style: none;
}
.pd-search-filter > .panel-body ul.filter-category > li a + ul > li > a {
padding-left: 30px;
}
.show {
display: block !important;
}
.hide {
display: none !important;
}
<script src="https://unpkg.com/vue"></script>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<div id="app">
<div class="panel panel-default pd-search-filter">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-circle-o"></i> By Category</h3>
</div>
<div class="panel-body">
<ul class="filter-category" v-for="list in categories">
<list-category :data="list" :category="category" @category="category = $event"></list-category>
</ul>
</div>
</div>
</div>
<template id="lc">
<li>
<!--parent-->
<a v-if="isFolder" href="javascript:" @click="toggle">
<span class="fa fa-fw" :class="icon"></span> {{data.name}}
</a>
<!--if not folding, we do not need an binding event-->
<a v-else href="javascript:" :title="data.name" :class="{active: data.id === categoryId}" @click="filterByCategory(data.id)"><span class="fa fa-fw fa-circle-o"></span> {{data.name}}</a>
<!--children-->
<ul v-if="isFolder" :class="isShow">
<list-category v-for="(data, index) in data.children" :key="index" :data="data" :search="search" :category="categoryId" @category="$emit('category', $event)"></list-category>
</ul>
</li>
</template>
Upvotes: 1
Reputation: 1448
You cannot control the data of a parent element from the child component. In order to change the parent's data yyou would need to emit the change to the parent and than change the data from the parent.
Please find the below to have an idea how to use the this.$emit
. I know I had to change the json data to avoid recursive calls to the same template, but now you have an idea on how to change the parent data element.
Vue.component('list-category', {
template: "#lc",
props: ['data', 'category', 'search'],
data() {
return {
open: false,
categoryId: this.category
}
},
mounted() {
let isDataOpen = (d) => d.id === this.categoryId || d.children && d.children.some(isDataOpen);
this.open = isDataOpen(this.data);
},
computed: {
icon() {
return {
'fa-plus': !this.open,
'fa-minus': this.open,
}
},
isFolder() {
return this.data.children && this.data.children.length
},
isShow() {
return this.open ? 'show' : 'hide'
}
},
methods: {
toggle() {
this.open = !this.open
},
filterByCategory: function(id){
this.$emit('update-active-category', id);
console.log('Emitting: ' + id);
}
}
})
new Vue({
el: '#app',
data() {
return {
categories: [{
id: 1,
name: 'England',
children: [{
id: 3,
name: 'Chelsea',
children: [{
id: 7,
name: 'Hazard'
},
{
id: 8,
name: 'Morata'
}
]
},
{
id: 4,
name: 'Manchester United',
children: [{
id: 9,
name: 'Pogba'
},
{
id: 10,
name: 'Lukaku'
}
]
}
]
},
{
id: 2,
name: 'Spain',
children: [{
id: 5,
name: 'Real Madrid',
children: [{
id: 11,
name: 'Ronaldo'
},
{
id: 12,
name: 'Bale'
}
]
},
{
id: 6,
name: 'Barcelona',
children: [{
id: 13,
name: 'Messi'
},
{
id: 14,
name: 'Suarez'
}
]
},
]
}
],
category: 7
}
},
methods: {
updateActiveCategory: function(id) {
this.category = id;
}
}
})
.active {
background: yellow !important;
}
.pd-search-filter > .panel-body ul.filter-category {
padding-left: 0;
list-style: none;
margin: 0 -15px 0;
}
.pd-search-filter > .panel-body ul.filter-category > li a {
display: block;
padding: 10px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pd-search-filter > .panel-body ul.filter-category > li a:last-child {
padding-left: 45px;
}
.pd-search-filter > .panel-body ul.filter-category > li a:focus, .pd-search-filter > .panel-body ul.filter-category > li a:hover {
background-color: #eeeeee;
text-decoration: none;
}
.pd-search-filter > .panel-body ul.filter-category > li a + ul {
padding-left: 0;
list-style: none;
}
.pd-search-filter > .panel-body ul.filter-category > li a + ul > li > a {
padding-left: 30px;
}
.show {
display: block !important;
}
.hide {
display: none !important;
}
<script src="https://unpkg.com/vue"></script>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<div id="app">
<div class="panel panel-default pd-search-filter">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-circle-o"></i> By Category</h3>
</div>
<div class="panel-body">
<ul class="filter-category" v-for="list in categories">
<list-category :data="list" :category="category" @update-active-category="updateActiveCategory">
</list-category>
</ul>
</div>
</div>
</div>
<template id="lc">
<li>
<!--parent-->
<a v-if="isFolder" href="javascript:" @click="toggle">
<span class="fa fa-fw" :class="icon"></span> {{data.name}}
</a>
<!--if not folding, we do not need an binding event-->
<a v-else href="javascript:" :title="data.name" :class="{active: data.id === category}" @click="filterByCategory(data.id)" @update-active-category="filterByCategory"><span class="fa fa-fw fa-circle-o"></span> {{data.name}}</a>
<!--children-->
<ul v-if="isFolder" :class="isShow">
<list-category v-for="(data, index) in data.children" :key="index" :data="data" :search="search" :category="category" @update-active-category="filterByCategory"></list-category>
</ul>
</li>
</template>
Upvotes: 0