I has a simple code to test comunicate between child and parent component follow example from vuejs doc : http://vuejs.org/guide/components.html#Using-v-on-with-Custom-Events. but apparently it does not work at the parent component
My jsfiddle: Jsfiddle
html:
Vue.component('tasks-item', {
template: '<div>{{item.title}} <button v-on:click="deleteItem(item)">x</button></div>',
props: ['item'],
methods: {
deleteItem: function(item){
console.log('child click')
document.getElementById('output').innerHTML='child click : '+item.title
this.$emit('deleteItem')
}
}
})
Vue.component('tasks-list', {
template: '#tasks-list',
props: ['tasks'],
methods: {
deleteTask: function(){
document.getElementById('output').innerHTML='parent click'
}
}
})
new Vue({
el: '#app',
data: function(){
return {
data:[{"id":51,"title":"rr4434","content":"rtrtrrtrtr"},{"id":50,"title":"rrrr","content":"rrr"},{"id":49,"title":"rrrr","content":"rrr"},{"id":48,"title":"rrr","content":"rrr"},{"id":47,"title":"rrr","content":"rrr"},
{"id":46,"title":"c\u00f4 d\u00e2\u0300n","content":"pha\u0309i khong em"},
{"id":45,"title":"we are you","content":"content"},
{"id":44,"title":"cai min nek","content":"co gi kh\u00f4ng"},{"id":43,"title":"abc","content":"dghjj"},{"id":42,"title":"dddd","content":"ddd"},{"id":38,"title":"444","content":"4444"},{"id":36,"title":"rrr","content":"rr"},{"id":35,"title":"rr","content":"rr"},{"id":34,"title":"rrrr","content":"rrr"},{"id":33,"title":"rrr","content":"rr"}]
}
},
methods: {
}
})
<script src="https://unpkg.com/vue#next/dist/vue.js"></script>
<div id="app">
<div id="output">click output</div>
<hr/>
<tasks-list :tasks="data"></tasks-list>
</div>
<template id="tasks-list">
<div>
<div v-for="item in tasks">
<tasks-item :item="item" v-on:deleteItem="deleteTask()"></tasks-item>
</div>
</div>
</template>
Change
this.$emit('deleteItem')
to
this.$emit('delete-item')
and inside template fix component's v-on from
v-on:deleteItem
to
v-on:delete-item
You can read more at https://v2.vuejs.org/v2/guide/components-custom-events.html
Related
I'm looking to toggle between v-html and insert as plain text in vue.js v2. So far I have this
HTML
<div id="app">
<h2 v-html="html ? text : undefined">{{html ? '' : text}}</h2>
<button #click="toggle">
switch
</button>
</div>
JS
new Vue({
el: "#app",
data: {
text:"Hello<br/>World",
html:false
},
methods: {
toggle: function(){
this.html = !this.html;
}
}
})
but this doesn't work when html is false. How can I get this to work? I'm looking for a solution where I don't need to repeat <h2> twice using a v-else. Preferably, if I can do it with just the 1 <h2> tag.
Thanks
Use v-bind with the prop modifier. see docs.
new Vue({
el: "#app",
data: {
text:"Hello<br/>World",
html:false
},
computed: {
headingProps() {
return this.html ? { innerHTML: this.text } : { innerText: this.text } ;
},
},
methods: {
toggle: function(){
this.html = !this.html;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<h2 v-bind.prop="headingProps"></h2>
<button #click="toggle">
switch
</button>
</div>
This is the parent page that I pass the data "time"&"breedKey" to child
<template>
<MyChildComponent v-bind:breedKey="breedKey" v-bind:time="time"> </MyChildComponent>
</template>
<script>
data() {
return {
time:[]
breedKey:[]
}
},
<script>
This is the child page that I successfully get the value, but value is not updated when the value in parent is changed.
props: ["breedKey", "time"],
data() {
return {
thedate: this.time,
topic: this.breedKey
}
The data are only initialized with the props value.
You can directly reference to the props if you want to have reactivity.
Vue.config.devtools = false;
Vue.config.productionTip = false;
const MyChildComponent = Vue.component('mychildcomponent', {
template: '#mychildcomponent',
props: ["breedKey", "time"]
})
var app = new Vue({
el: '#app',
components: {
MyChildComponent
},
data() {
return {
time : [],
breedKey: []
}
},
methods: {
addTime() {
this.time.push(Math.floor(Math.random() * 100));
},
addKey() {
this.breedKey.push(Math.floor(Math.random() * 100));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#3.1.2/dist/vuex.js"></script>
<div id="app">
<div>
<button #click="addTime">Add time</button>
<button #click="addKey">Add key</button>
<mychildcomponent v-bind:breed-key="breedKey" v-bind:time="time" />
</div>
</div>
<div>
<div style="display:none">
<div id="mychildcomponent">
<div>
time : {{ time }}
breedKey : {{ breedKey }}
</div>
</div>
</div>
I have a single file component like this:
<template>
<div>
<template v-if="offers.length > 3">
View all offers here
</template>
<template v-else-if="offers.length > 1">
<offer v-for="offer in offers" :data="offer"></offer>
</template>
<template v-else-if="offers.length == 1">
<offer :title="The offer" :data="offers[0]"></offer>
</template>
</div>
</template>
Based on the number of offers, I choose how many to render.
Question: How do I efficiently get/count the number of <offer> components? I also need that number to be reactive.
There's no clean way how.
You could count the children of the current instance that are of a specific type. But you would have to call the "recount" logic on update hook (as well as mounted).
Example:
Vue.component('offer', {
name: 'Offer',
template: '<span> offer </span>'
})
new Vue({
el: '#app',
data: {
offers: [1, 2],
offerCount: 0
},
methods: {
updateOfferCount() {
this.offerCount = this.$children.filter(child => child.constructor.options.name === 'Offer').length;
}
},
updated() {
this.updateOfferCount()
},
mounted() {
this.updateOfferCount()
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div>
<template v-if="offers.length > 3">
View all offers here
</template>
<template v-else-if="offers.length > 1">
<offer v-for="offer in offers" :data="offer"></offer>
</template>
<template v-else-if="offers.length == 1">
<offer :data="offers[0]"></offer>
</template>
</div>
<br>
<button #click="offers.push(123)">Add Offer</button> offerCount: {{ offerCount }}
</div>
I'm answering this based solely on the idea that you want to count instantiations and destructions of Offer components. I'm not sure why you don't just count offers.length. Maybe other things can trigger instantiations.
Have the component emit events on creation and destruction and have the parent track accordingly.
Alternatively (and maybe overkill) you could use Vuex and create a store that the Offer commits to on creation and destruction. This means that you don't have to manually attach #offer-created/destroyed directives every time you put an <offer> in your markup.
Both methods are included in the following example:
const store = new Vuex.Store({
strict: true,
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
}
});
const Offer = {
props: ["data"],
template: "<div>{{data.name}}</div>",
created() {
console.log("Created");
this.$emit("offer-created");
this.$store.commit("increment");
},
destroyed() {
console.log("Destroyed");
this.$emit("offer-destroyed");
this.$store.commit("decrement");
}
};
const app = new Vue({
el: "#app",
store,
components: {
offer: Offer
},
data() {
return {
offers: [],
offerCount: 0
};
},
computed: {
offerCountFromStore() {
return this.$store.state.count;
}
},
methods: {
offerCreated() {
this.offerCount++;
},
offerDestroyed() {
this.offerCount--;
},
addOffer() {
this.offers.push({
name: `Item: ${this.offers.length}`
});
},
removeOffer() {
this.offers.pop();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<div>Offer instances: {{offerCount}}</div>
<div>Offer instances (from store): {{offerCountFromStore}}</div>
<div>
<div v-if="offers.length > 3">
View all offers here
</div>
<div v-else-if="offers.length > 1">
<offer #offer-created="offerCreated" #offer-destroyed="offerDestroyed" v-for="offer in offers" :data="offer"></offer>
</div>
<div v-else-if="offers.length == 1">
<offer #offer-created="offerCreated" #offer-destroyed="offerDestroyed" :data="offers[0]"></offer>
</div>
</div>
<div>
<button #click.prevent="addOffer">Add</button>
<button #click.prevent="removeOffer">Remove</button>
</div>
</div>
The problem with trying to use $children is that it is, inherently, not reactive:
The direct child components of the current instance. Note there’s no
order guarantee for $children, and it is not reactive. If you find
yourself trying to use $children for data binding, consider using an
Array and v-for to generate child components, and use the Array as
the source of truth.
I've an issue in this code
let bus = new Vue();
Vue.component('building-inner', {
props: ['floors', 'queue'],
template: `<div class="building-inner">
<div v-for="(floor, index) in floors" class="building-floor" :ref="'floor' + (floors - index)">
<h3>Floor #{{floors - index }}</h3>
<button type="button" class="up" v-if="index !== floors - floors">up</button>
<button type="button" class="down" v-if="index !== floors - 1">down</button>
</div>
</div>`,
beforeMount(){
bus.$emit('floors', this.$refs);
}
})
Vue.component('elevator', {
data: {
floorRefs: null
},
props: ['floors', 'queue'],
template: `<div class="elevator" ref="elevator">
<button type="button" v-for="(btn, index) in floors" class="elevator-btn" #click="go(index + 1)">{{index + 1}}</button>
</div>`,
beforeMount() {
bus.$on('floors', function(val){
this.floorRefs = val;
console.log(this.floorRefs)
})
},
methods: {
go(index) {
this.$refs.elevator.style.top = this.floorRefs['floor' + index][0].offsetTop + 'px'
}
}
})
new Vue({
el: '#building',
data: {
queue: [],
floors: 5,
current: 0
}
})
<div class="building" id="building">
<elevator v-bind:floors="floors" v-bind:queue="queue"></elevator>
<building-inner v-bind:floors="floors" v-bind:queue="queue"></building-inner>
</div>
I tried to access props inside $refs gets me undefined, why?
You should use a mounted hook to get access to the refs, because on "created" event is just instance created not dom.
https://v2.vuejs.org/v2/guide/instance.html
You should always first consider to use computed property and use style binding instead of using refs.
<template>
<div :style="calculatedStyle" > ... </div>
</template>
<script>
{
//...
computed: {
calculatedStyle (){
top: someCalculation(this.someProp),
left: someCalculation2(this.someProp2),
....
}
}
}
</script>
It's bad practice to pass ref to another component, especially if it's no parent-child relationship.
Refs doc
Computed
I wanted to create a tree view from an XML file, and I did this. However, when I decided to make it more flexible I encountered some problems.
Here are my components:
Vue.component('elname', {
props: ['text'],
template: '<span>{{ text }}</span>'
})
Vue.component('recursive', {
props: ['d', 'liname', 'openclose'],
template: '#recursive',
data: function() {
return {
seen: true
}
}
}
)
and the Vue object looks like this:
var appp = new Vue({
el: '#here',
data: function(){
return {
friends: '',
}
},
beforeMount() {
parser = new DOMParser();
var response = "<scope><friend><name>Alex</name><hobbies><h>music</h><h>salsa</h></hobbies></friend><friend><name>Natasha</name><hobbies><h>hiking</h></hobbies></friend></scope>";
xml = parser.parseFromString(response, 'text/xml');
children = xml.getElementsByTagName('scope')[0];
this.friends = children;
}
})
I have this variable seen in recursive component
Vue.component('recursive', {
props: ['d', 'liname', 'openclose'],
template: '#recursive',
data: function() {
return {
seen: true // <-- here it is
}
}
}
)
It must change its value #click event to hide a nested list (please, see the JSfiddle), but when it changes it updates its value IN SEVERAL components.
How to make its value be updated only in a particular component?
Here is a template:
<div id="here">
<recursive :d="friends" openclose="[-]"></recursive>
</div>
<template id="recursive">
<div>
<ul v-if="d.children.length != 0">
<li v-for="n in d.childNodes" #click="seen = !seen">
<elname :text="n.tagName"></elname>
{{ openclose }}
{{seen}} <!-- it is just for testing purposes to illustrate how seen var changes -->
<recursive :d="n" openclose="[-]"></recursive>
</li>
</ul>
<ul v-else>
<elname :text="d.textContent"></elname>
</ul>
</div>
</template>
You have two issues:
You need to use click.stop so that the click event doesn't propagate to parents
You need a component inside your recursive to handle the toggling
Vue.component('elname', {
props: ['text'],
template: '<span>{{ text }}</span>'
});
Vue.component('recursive', {
props: ['d', 'openclose'],
template: '#recursive',
components: {
toggler: {
data() {
return {
seen: true
}
},
methods: {
toggle() {
this.seen = !this.seen;
}
}
}
}
});
var appp = new Vue({
el: '#here',
data: function() {
return {
friends: '',
}
},
beforeMount() {
parser = new DOMParser();
var response = "<scope><friend><name>Alex</name><hobbies><h>music</h><h>salsa</h></hobbies></friend><friend><name>Natasha</name><hobbies><h>hiking</h></hobbies></friend></scope>";
xml = parser.parseFromString(response, 'text/xml');
children = xml.getElementsByTagName('scope')[0];
this.friends = children;
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js" integrity="sha256-Ab5a6BPGk8Sg3mpdlsHzH6khPkniIWsvEuz8Fv/s9X8=" crossorigin="anonymous"></script>
<div id="here">
<recursive :d="friends" openclose="[-]"></recursive>
</div>
<template id="recursive">
<div>
<ul v-if="d.children.length != 0">
<li is="toggler" v-for="n in d.childNodes" inline-template>
<div #click.stop="toggle">
<elname :text="n.tagName"></elname>
{{ openclose }}
<recursive v-if="seen" :d="n" openclose="[-]"></recursive>
</div>
</li>
</ul>
<ul v-else>
<elname :text="d.textContent"></elname>
</ul>
</div>
</template>
Currently you have 1 seen variable on an element, which controls the state for all child-elements. So a click on any child will change the seen value in the parent and show/hide all children of this parent.
Solution 1
Change the type of your seen variable to an array - with the same length as the children array. And change your handler to #click="seen[i] = !seen[i]"
Solution 2
Move the click listener to the children. So put #click="seen = !seen" on your outermost div in the template and render the whole list only on v-if="d.children.length && seen"
Vue.component( 'recursive-list', {
props: ["d"],
data: () => ({ expand: true }),
template: `<div style="margin: 5px">
<div v-if="Array.isArray(d)"
style="border: 1px solid black">
<button #click="expand = !expand">Show/Hide</button>
<template v-show="expand">
<recursive-list v-for="e in d" :d="e" />
</template>
<p v-show="!expand">...</p>
</div>
<p v-else>{{d}}</p>
</div>`
} )
new Vue({
el: '#main',
data: { d: ["Text", ["a","b","c"],[[1,2,3],[4,5,6],[7,8]]]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id='main'>
<h3>List:</h3>
<recursive-list :d="d"></recursive-list>
</div>
I've some modifications on your structure, maybe it's not exactly what you need but I think will became more clear.
<template id="tree">
<div>
<ul v-for="(tree, k, idx) in tree.childNodes">
<node :tree="tree" :idx="idx"></node>
</ul>
</div>
</template>
<template id="node">
<li>
<div v-if="tree.childNodes.length">
<span #click="seen = !seen">{{ tree.tagName }}</span>
<span>{{ seen }}</span>
<ul v-for="(node, k, id) in tree.childNodes">
<node :tree="node" :idx="id"></node>
</ul>
</div>
<div v-else>{{ tree.textContent }}</div>
</li>
</template>
https://jsfiddle.net/jonataswalker/Lw52t2dv/