Can I get computed data from child component to parent component? - javascript

Is there any way to get computed data from child component to parent component? Because I'm sending the data from parent to child first and then I want to use the comuted property (data) in the parent component. I want to do that because I want to reuse that important component (child) in other components too.
I have a search input field for filtering my items, and when i wrote something down i wanna get back that list from the child component.
Parent component
<input class="form-control form-control-search m-input" autocomplete="off" type="text" v-on:input='testFunc()' v-model="search" placeholder="Search...">
<paginate-links v-if="items.length > 0" :models="items">
<div class="m-list-timeline__item no-timeline" v-for="item in filterItems" v-bind:key="item.id">
{{ item.title }}
</div>
</paginate-links>
Child component
props: ['item']
computed: {
filterItems () {
return filter // here goes my code
}
}
So can i get the filterItems in the parent component?

There are a couple ways you can send data back to the parent, but probably the easiest way I'd say is using an emit within the computed.
in child:
computed:{
myVal() {
let temp = this.num + 1;
this.$emit('onNumChange', temp);
return temp;
}
}
in parent template:
<my-child-component #onNumChange="changeHandler"/>
in parent script
methods: {
changeHandler(value) {
console.log('Value changed to: ', value);
}
}
You could do a similar thing with watch, or pass a function from parent as a prop that notifies the parent, but my recommended way would be to use vuex
https://vuex.vuejs.org/en/

Related

Trying to emit result from bus vuejs

I am trying to emit result number of quotes where I add plus one and also push result of the text from textarea to array. The string text is pushed to array successfully but the number of quotes is always zero. I have tried it in one method or separed and I always get same result.
<template>
<div>
<p>Quotes</p>
<b-form-textarea
id="textarea-rows"
v-model="value"
placeholder="Enter something..."
rows="5"
></b-form-textarea>
<b-button variant="primary" #click='plusOneQuote(); addQuote();'>Add Quote</b-button>
</div>
</template>
<script>
import {EventBus} from '../main.js'
export default {
props: ['numberOfQuotes', 'quotes'],
data (){
return {
value: '',
}
},
methods: {
plusOneQuote() {
this.numberOfQuotes++;
EventBus.$emit('addedQuote', this.numberOfQuotes);
},
addQuote() {
this.quotes.push(this.value);
}
}
}
</script>
When I delete the addQuote from click and the method, then numberOfQuotes get bigger by one and I show that in another component by no problem.
Is a good practice to call a single method on the click directive, so instead of:
<b-button variant="primary" #click='plusOneQuote(); addQuote();'>Add Quote</b-button>here
you could implement
<b-button variant="primary" #click='updateQuotes'>Add Quote</b-button> here
And the methods section
methods: {
plusOneQuote() {
this.numberOfQuotes++;
EventBus.$emit('addedQuote', this.numberOfQuotes);
},
addQuote() {
this.quotes.push(this.value);
},
updateQuotes(){
this.plusOneQuote();
this.addQuote();
}
I had this problem before and hope this solution helps.
EDIT:
Sorry, missed some context, since you are trying push to this.quotes which is a prop, you cannot directly mutate it, you should use a getter and a setter to do it or even better, use a v-model. Quoting the docs:
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: "visible" (found in component )

How to use global variable in parent component and 3 child component in Vue js

I have created 3 steps from using 3 child components. All these three components are linked in one parent component.
What I need to do is:
Show these three forms as steps depending on step number which I need to be a global variable.
Update depends on each form call to action button.
Can anyone help on this?
You will need 2 things
To pass a property to the components in order to know the current step value
To emit an event from each component so the parent component will get notified about the new value that the step should be updated
I write the code below and give the explanation later
Your html
<div id="app">
</div>
Parent Component
var instance = new Vue({
el:'#app',
data:{
step:1
},
methods:{
stepChanged:function(step){
alert('Moving to step:'+step);
this.step = step;
}
},
template:`
<div>
<app-stepone v-if="step==1" v-on:stepChanged="stepChanged" :step="step" >
</app-stepone>
<app-steptwo v-if="step==2" v-on:stepChanged="stepChanged" :step="step" >
</app-steptwo>
<app-stepthree v-if="step==3" v-on:stepChanged="stepChanged" :step="step">
</app-stepthree>
</div>
`
});
Step 1 component
Vue.component('app-stepone',{
props:['step'],
data:function(){
return {};
},
methods:{
moveStep(){
this.$emit('stepChanged',2)
}
},
template:`<div>We are in Step 1 - {{step}}<br /><button v-
on:click="moveStep()">Move to Step 2</button></div>`
});
Step 2 Component
Vue.component('app-steptwo',{
props:['step'],
data:function(){
return {};
},
methods:{
moveStep(){
this.$emit('stepChanged',3)
}
},
template:`<div>We are in Step 2 - {{step}}<br /><button v-
on:click="moveStep">Move to Step 3</button></div>`
});
Step 3 Component
Vue.component('app-stepthree',{
props:['step'],
data:function(){
return {};
},
methods:{
moveStep(){
this.$emit('stepChanged',1)
}
},
template:`<div>We are in Step 3 - {{step}}
<br />
<button v-on:click="moveStep">Move to first step</button>
</div>`
});
Each component can receive the step property from the parent by just passing to it with the name :step="step" and registering a props:['step'] in every app-step component and then inside the step component you can use this property and know the current value (in the current example i do not use it, but i show how you can receive it )
Each component after it does its calculation or whatever your business logic is, can emit to the parent the change that would like to be applied for the step. The component should notify the parent by running this command this.$emit('stepChanged','<value of the step>') .
In order for the parent component to listen for the change, it should register to each component a handler that will have the name 'stepChanged' and the method that will be called

Getting the event source from custom events in Vue?

I'm building a Vue component that consists of an unspecified number of child components. Only one child component is visible at all times, and the user can only switch between child components when the one currently visible has emitted an is-valid event.
I want to keep this decoupled, such that children do not know about their parent and only communicate by emitting events. This also means that the children do not know their position within the parent component.
So, the parent component somehow has to keep track of which child the event came from. If the event came from the right child (the one currently visible) then some buttons are activated that allows the user to go to the next or previous child.
Here's my code so far:
HTML
<div id="app">
<template id="m-child">
<div>
<button v-on:click="setstate(true)">Valid</button>
<button v-on:click="setstate(false)">Invalid</button>
</div>
</template>
<template id="m-parent">
<div>
<m-child v-on:newstate="newchildstate"></m-child>
<m-child v-on:newstate="newchildstate"></m-child>
<m-child v-on:newstate="newchildstate"></m-child>
</div>
</template>
<m-parent></m-parent>
</div>
JS
Vue.component('m-child', {
template: '#m-child',
data: function() {
return {};
},
methods: {
setstate: function (valid) {
this.$emit('newstate', valid);
}
}
});
Vue.component('m-parent', {
template: '#m-parent',
methods: {
newchildstate: function (valid) {
console.log('valid:' + valid + ', but where from?');
}
}
});
new Vue({
el: '#app'
});
Of course I could hardcode an index on the child event binding:
<m-child v-on:newstate="newchildstate(0, $event)"></m-child>
<m-child v-on:newstate="newchildstate(1, $event)"></m-child>
<m-child v-on:newstate="newchildstate(2, $event)"></m-child>
But that would make the whole setup a lot less modular, I just want to be able to plug in a number of children in the DOM and make it work right away.
I've looked at the API for Vue events and there doesn't seem to be a way to get the source from the event object.
This depends on what you want to receive back, my personal preference is to pass in a prop to set a unique id and pass it back in the $emit:
<m-child v-on:newstate="newchildstate" :id="1"></m-child>
<m-child v-on:newstate="newchildstate" :id="2"></m-child>
<m-child v-on:newstate="newchildstate" :id="3"></m-child>
Then in your child component you can emit an object with the state and id the id:
Child:
this.$emit('newstate', {id: this.id, state: valid});
Parent:
newchildstate: function (valid) {
console.log('valid:' + valid.state + ', from' + valid.id);
}
I realise that this doesn't look hugely different from your hard coded example, but at some point your parent is going to want to deal with the event, so you could set up an array in data with the initial states and then use a v-for:
data: {
children: [true, false, false] // setup states
}
You would then do:
<div v-for="(state, index) in states">
<m-child v-on:newstate="newchildstate" :id="index"></m-child>
</div>
And in your view model:
methods: {
newchildstate: function(valid) {
this.$set(this.states, valid.id, valid.state);
}
}
Here's a JSFiddle that initiates the array dynamically via a prop and sets up the child components: https://jsfiddle.net/2y9727e2/

Detect component content change in Ractive.js

Is there any way of detecting when the content of a component changes?
For example:
HTML:
<component>
<div>{{name}}</div>
</component>
JS:
component = Ractive.extend({
on...: function () {
// invoked when the inner HTML changes
}
});
I use {{yield}} so the content is rendered in the context of the parent.
For now I'll have to pass name to the component just for the purpose of observing changes (even though I don't need the value in the context of the component). (Or I'll add a function that I can call).
<component changes="{{name}}">
<div>{{name}}</div>
</component>
Any ideas?
It's possible, but a bit hacky.
http://plnkr.co/edit/YoTZpyTTyCyXijEteGkg?p=preview
<comp>
{{name}} hi {{thing}}
</comp>
comp = Ractive.extend({
template: '<div>{{yield}}</div>',
oncomplete: function() {
var self = this;
self.partials.content.forEach(function(partial) {
if (partial.r) {
self.parent.observe(partial.r, function(newValue) {
console.log(partial.r + ' changed to ', newValue)
}, {init: false});
}
});
}
})
Yield/Content are really just special partials, so this will loop through the items in that partial and set up an observer for each keypath.
This demo only works with simple expressions like {{foo}}. If you have more complicated things inside the partial you'll have to inspect the rendered partial to figure out what keypath you want to observe the parent on.

DOM element to corresponding vue.js component

How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}

Categories