I got a problem:
I included a custom component like that:
{{item.lists}}
<div v-for="(listitem, index) in item.lists" :key="index"
class="media align-items-center bg-white ui-bordered py-3 mb-2">
<div class="component-move ion ion-ios-move text-muted text-big p-4"></div>
<div class="media-body">
<div class="container mt-2">
<div class="row">
<viewlist :item="listitem" :name="`${listitem.id}-${index}`"/>
</div>
</div>
</div>
</div>
So now there's an edit component:
<editlistitem :name="item" :item="ListItem"/>
Inside this edit component, there's a watcher:
watch: {
'item.value': function (value) {
let vm = this;
_.each (vm.item.children, function (child) {
child.id === value ? child.value = true : child.value = false;
});
}
},
If the value of the listitem changes, the value of the child is set to false or true.
These changes are reflected in {{item.lists}} in my parent view. But
<viewlist :item="listitem" :name="`${listitem.id}-${index}`"/>
does not show the changes. It sticks to the old values.
It can be Vue reactivity problem.
You can try use Vue.set
_.each (vm.item.children, (child, index) => {
const newChild = {...child, value: child.id === value }
vm.$set(vm.item.children, index, newChild)
});
The better way is emitting an event to parent and change data in parent since Vue doesn't encourage to mutate props in children component.
Related
I am trying to use V-Animate-CSS to show a "deletion" animation when a delete button is pressed. I am struggling with trying to specify the exact element to delete programmatically within a v-for loop.
Let me explain:
I have the following vue <template> like so:
<div
v-for="x in divisionLangs"
:key="x.P_uID"
>
<button
type="button"
#click.prevent="deleteCard(x.P_uID)"
>
</button>
<transition name="bounce"
<div v-if="show" class="card-body">
<!-- card content is here -->
</div>
</transition>
My <script> section looks like so:
data() {
return {
show: true,
divisionLangs: []
}
}
deleteCard(id) {
this.show = !this.show
this.divisionLangs = this.divisionLangs.filter(x => x.P_uID !== id)
},
The data for the divisionLangs array looks like so:
[
{
P_uID: 789,
..blah...
},
{
P_uID: 889,
...blah...
}
]
How can I structure this code so only the matching card item is deleted from the rendered list and not ALL of the card items? What happens right now is that all of the cards are deleted on the deleteCard method.
You should define a child component for each item like below:
Parent component:
<tmplate>
<child-component v-for="x in divisionLangs" :key="x.P_uID" />
</template>
And in child component:
<button
type="button"
#click.prevent="deleteCard(x.P_uID)"
>
</button>
<transition name="bounce"
<div v-if="show" class="card-body">
<!-- card content is here -->
</div>
</transition>
And Script part of child component:
data() {
return {
show: true,
divisionLangs: []
}
}
deleteCard(id) {
this.show = !this.show
this.divisionLangs = this.divisionLangs.filter(x => x.P_uID !== id)
},
//this code in vue js for accordion this is working but not show open only //one at the same time.
//there component and pass props data.
<packing-material-category v-for="(category, index) in packingMaterialCategories" :category="category" :key="index"></packing-material-category>
//there template to render data.
<template>
<div class="packing-categories">
<div :class="packingCategoryClass">
<h3 class="packing-category__title" #click="toggleAccordion(category.title)" v-text="category.title" />
<div v-show="accordionOpen===true" class="packing-category__content">
<div v-if="category.description" class="packing-category__description" v-text="category.description" />
</div>
</div>
</div>
//call method for open and close
toggleAccordion() {
this.accordionOpen = !this.accordionOpen;
}
You need to track which item is shown outside of packing-material-category component (value of openedIndex) and to pass that value down to components. When value changes in the packing-material-category component, you emit event change-accordion, and in your parent component you update value of openedIndex
Try this
In your parent component, add openedIndex: 0 to data.
If you want everything closed by default, set value to false.
Update template to pass down the props index and openedIndex, so that components know when to show/hide.
<packing-material-category
v-for="(category, index) in packingMaterialCategories"
:key="index"
:index="index"
:openedIndex="openedIndex"
:category="category"
#change-accordion="(newOpen) => openedIndex = newOpen"
>
</packing-material-category>
And the packing-material-category component could look like this
<template>
<div class="packing-categories">
<div :class="packingCategoryClass">
<h3 class="packing-category__title" #click="toggleAccordion" v-text="category.title" />
<div v-show="index === openedIndex" class="packing-category__content">
<div v-if="category.description" class="packing-category__description" v-text="category.description" />
</div>
</div>
</div>
</template>
<script>
export default {
props: [
'category',
'index', // index of this component
'openedIndex', // which item should be shown/opened
],
data() {
return {
packingCategoryClass: '',
}
},
methods: {
toggleAccordion() {
// Show current item. If already opened, hide current item
let value = this.index === this.openedIndex ? false : this.index;
// notify parent component about the change
this.$emit('change-accordion', value)
}
}
}
</script>
Scenario
I have a custom button component in Vue:
<custom-button type="link">Save</custom-button>
This is its template:
// custom-button.vue
<template>
<a v-if="type === 'link'" :href="href">
<span class="btn-label"><slot></slot></span>
</a>
<button v-else :type="type">
<span class="btn-label"><slot></slot></span>
</button>
</template>
You can see from the template that it has a type prop. If the type is link, instead of the <button> element, I am using <a>.
Question
You'll notice from the template that I repeated the child component, i.e. <span class="btn-label"><slot></slot></span> on both root components. How do I make it so that I won't have to repeat the child components?
In JSX, it's pretty straightforward. I just have to assign the child component to a variable:
const label = <span class="btn-label">{text}</span>
return (type === 'link')
? <a href={href}>{label}</a>
: <button type={type}>{label}</button>
In this situation, I would probably opt to write the render function directly since the template is small (with or without JSX), but if you want to use a template then you can use the <component> component to dynamically choose what you want to render as that element, like this:
Vue.component('custom-button', {
template: '#custom-button',
props: [
'type',
'href',
],
computed: {
props() {
return this.type === 'link'
? { is: 'a', href: this.href }
: { is: 'button', type: this.type };
},
},
});
new Vue({
el: '#app',
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<custom-button type="button">Button</custom-button>
<custom-button type="submit">Submit</custom-button>
<custom-button type="link" href="http://www.google.com">Link</custom-button>
</div>
<template id="custom-button">
<component v-bind="props">
<span class="btn-label"><slot></slot></span>
</component>
</template>
Well you could always create a locally registered component...
// in custom-button.vue
components : {
'label' : {template : '<span class="btn-label"><slot></slot></span>'}
}
Here what appears in the console when I run my code:"Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "isChecked"".
I have seen what other posts say about it but i can't adapt it on my problem.
Could someone explain it to me please?
PARENT:
template:
<div class="checkBox-container">
<input type="checkbox"/>
<!-- <div class="check" :class="[size]" v-if="!isChecked"></div>
<div class="check" :class="[size]" v-else>
<div>v</div>
</div> -->
<div class="check" :class="[size]" #click="changeVal">
<div v-if="isChecked">v</div>
</div>
<div><label class="label">{{label}}</label></div>
<div><span class="subLabel">{{subLabel}}</span></div>
script:
export default {
name: "ax-checkbox",
props: {
label: String,
subLabel: String,
size: String,
isChecked: false,
checks: []
},
methods: {
changeVal() {
this.isChecked = !this.isChecked;
this.$emit("changeVal");
}
}
};
CHILD
<div class="filters">
<ax-checkbox label="Où :" subLabel="Ville" size="small"></ax-checkbox>
<div class="separator"></div>
<ax-checkbox label="Quoi :" subLabel="Thématique(s)" size="small"></ax-checkbox>
<div class="separator"></div>
<ax-checkbox label="Quand :" subLabel="Dans..." size="small"></ax-checkbox>
</div>
The prop you are mutating is isChecked.
So, create a local data variable (initialized it with isChecked) and mutate it instead:
export default {
name: "ax-checkbox",
props: {
label: String,
subLabel: String,
size: String,
isChecked: false,
checks: []
},
data() {
return {isCheckedInternal: this.isChecked}
},
methods: {
changeVal() {
this.isCheckedInternal = !this.isCheckedInternal;
this.$emit("changeVal");
}
}
};
And replace it in the template:
<div class="checkBox-container">
<input type="checkbox"/>
<!-- <div class="check" :class="[size]" v-if="!isCheckedInternal"></div>
<div class="check" :class="[size]" v-else>
<div>v</div>
</div> -->
<div class="check" :class="[size]" #click="changeVal">
<div v-if="isCheckedInternal">v</div>
</div>
<div><label class="label">{{label}}</label></div>
<div><span class="subLabel">{{subLabel}}</span></div>
Note: The code above will use the prop isChecked only as initializer. If the parent changes in any way the value it passed to isChecked, the child component will not pick that change up. If you want to pick it up, add a watch in addition to the proposed code above:
//...
watch: {
isChecked(newIsChecked) {
this.isCheckedInternal = newIsChecked;
}
}
};
Ideal
There are some possible improvements to your code. Here are some suggestions:
subLabel prop should be sub-label
instead of emitting a changeVal value, emit update:isChecked and then you can use :is-checked.sync="myCheckedValue" in the parent.
This way you can still bind internally to the prop isChecked and not change it, but emit events and react to when the parent changes isChecked instead.
If you wanted to go the extra mile (and think it is worth it), you could also add a model option to your component, so you can be able to use v-model instead of :is-checked.sync.
See demo below.
Vue.component("ax-checkbox", {
template: '#axCheckboxTemplate',
props: {
label: String,
subLabel: String,
size: String,
isChecked: false,
checks: []
},
model: { // <== this part to will also enable v-model besides :is-checked.async
prop: 'isChecked',
event: 'update:isChecked'
},
methods: {
updateIsChecked() {
this.$emit("update:isChecked", !this.isChecked);
}
}
})
new Vue({
el: '#app',
data: {
myCheckedValueSync: false, myCheckedValueVModel: false,
}
});
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<template id="axCheckboxTemplate">
<div class="checkBox-container">
<input type="checkbox" :checked="isChecked" #change="updateIsChecked" />
<div class="check" :class="[size]" #click="updateIsChecked">
CLICK ME<div v-if="isChecked">v</div>
</div>
<label class="label">{{label}}</label><span class="subLabel">{{subLabel}}</span>
</div>
</template>
<div id="app">
<div class="filters">
<pre>parent's myCheckedValueSync: {{ myCheckedValueSync }}</pre>
<ax-checkbox label="Où :" sub-label="Ville" size="small" :is-checked.sync="myCheckedValueSync">
</ax-checkbox>
<pre>parent's myCheckedValueVModel: {{ myCheckedValueVModel }}</pre>
<ax-checkbox label="Quoi :" sub-label="Thématique(s)" size="small" v-model="myCheckedValueVModel">
</ax-checkbox>
</div>
</div>
As it appears you're trying to update the value of a property passed into your component, your component is in fact a custom input component. Take a look at the excellent Vue docs on this topic. I've summarised the idea below.
2-way databinding in Vue is handled by v-model. Applied to your isChecked property this comes down to v-model="isChecked", which is in fact syntactic sugar for :value="isChecked" #input="evt => isChecked = evt.target.value".
Thus for your component, you need to do the following:
Update the name of the isChecked property to value
In your changeVal method, emit an input event, like:
changeVal() { this.$emit("input", !this.value); }
If need be, you could still also emit the changeVal event.
I have the following markup in a parent component
....
<div class="container-fluid">
<claims :userID="userId"></claims>
<claimForm :claimID="claimId" v-if="isDistributor"></claimForm>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<display :claimID="claimId"></display>
...........
data(){ return { claimId: null } },
beforeMount(){
this.userId = this.isDistributor? this.currentUser.id: null
this.eventEmitter.$on('claim.details', function(id) {
this.claimId = id
})
}
Then in the child component known as <display>
, watch: {
.....
claimID: function(n) {
console.log('claim');
if(n == null) return false
this.getClaimById()
}
.....
when an event bus this.eventEmitter.$emit() is emitted the parent component data claimdId is updated however the child component's claimID property doesn't seem to be updated thus the "watch" is not triggered.
What would cause the property to not be updated?
As explained here:
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents:
so your code should be:
<claimForm :claim-iD="claimId" v-if="isDistributor"></claimForm>