Best way to a vue instance (app1) call another vue instance (app2) - vue.js

Example:
<div id="app1"> <button #click="etc...">run APP1</button> ..</div>
<div id="app2">...
<button #click...>run APP1</button><!-- HERE! need a reference--> ...
<button #click...>run APP2</button>
</div>
How to say to Vue in APP2 that I need to call APP1?
I need that both buttons "run APP1" do exactly the same thing. Supposing that, in the illustration, the first is working, and the second is a copy/paste... But the second will not work because is into app2.
Concrete case: see "GOOD1" and "GOOD2" buttons at this code similar to the used in the last question...

Every Vue instance implements an events interface. This means that to communicate between two Vue instances (app1 and app2) you can use Custom Events.
So, in your example, before anything, give the first instance (that will emit events) a name so the other instance can reference it:
var app1 = new Vue({
el: '#app1'
})
And emit events from it (this code is at app1's template):
<button #click="$emit('go-modal', {header: 'HEL<big>LO2</big>', showModal: true})">GOOD1</button>
<button #click="$emit('go-modal', {header: 'BYE2', showModal: true})">GOOD2</button>
In the second instance (app2), listen to those events using $on:
new Vue({
el: '#app2',
// ...
mounted() {
app1.$on('go-modal', this.handleApp1);
},
methods: {
handleApp1(data) {
this.header = data.header;
this.showModal = data.showModal;
}
}
})
See demo JSFiddle here.

Related

Vue data vue object variables from component

I am very new to Vue and I am having difficulty accessing data in main Vue instance from component. In the Vue instance I have:
var vm = new Vue({
computed: {},
data: {
show: true
},
and in the component, I want to do something like:
<button v-show="vm.show" #click="setDefaults(styleguide)">Set Default</button>
My goal is when 'show' value changes, I want to display/hide the button. It is little difficult/weird because I create template in the component, not in the html. When I try this code, it doesn't understand 'vm.show'. I feel like I need to create data in the component and tie the data to the 'show' variable, or create computed in the component or something (I believe computed is like watcher?). If there is easy way to handle this, please help.
I'm also new to VueJs, but I believe the issue is you haven't provided the el argument to the Vue instance, and in this case assigning the Vue instance to a variable doesn't do anything.
I think you want something like
<div id="app">
<button v-show="show" #click="setDefaults(styleguide)">Set Default</button>
</div>
<script>
new Vue({
el: '#app',
computed: {},
data: {
show: true
},
...
);
</script>
So I guess my question wasn't very clear, but I got it to figure it out. In the component code, I needed to add:
Vue.component('styleguide', {
computed: {
show: function () {
return vm.show
}
},
props: ['styleguide'],
template: `
<div>
<div>
<p>
<button v-show="show" #click="setDefaults(styleguide)">Set Default</button>
This allows me to access 'show' in the main Vue instance from the component template. Whenever other component modifies 'show' variable in the main Vue instance, the button disappears/reappears. I am not sure if this makes sense, but this is how I got it to work.
Two things, in the template all variables are already scoped from the component so you don't need the vm. in there. The second thing is that the data property of a component expects a function which returns an object.
var vm = new Vue({
computed: {},
data: () => ({
show: true
}),
<button v-show="show" #click="setDefaults(styleguide)">Set Default</button>
If you need to access data from a parent component you will need to pass it on using props. you can also try to do it using provide/inject if that suits your usecase better

Trigger store action from outside vue app

I want to include buttons in various places on a page, outside the Vue components elsewhere on the page, that trigger store actions through a click event. I have buttons within my components that do as much, connecting to methods after v-on:click that then trigger store actions through this.$store.dispatch. Is it possible to do this from outside of Vue?
To directly answer your question, you can do what you want by hanging your Vue instance directly off of the window, for instance:
<div id="app">
<p>{{ message }}</p>
</div>
window.IVue = new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
store: new Vuex.Store({
actions: {
hello() {
return Promise.resolve('Hello from the store!')
}
}
})
})
window.IVue.$store.dispatch('hello').then((message) => {
Vue.set(window.IVue, 'message', message)
})
And here's a jsFiddle
You'll see the message is Hello from the store!

vue component is not passing events through

I have three nested components
<parent #event="handle">
<inbetween #event="$emit('event')">
<child> // emits event
</child>
</inbetween>
</parent>
so, when a child emits the event, I have to add that annoying part #event="$emit('event')" to the component inbetween, otherwise parent does not receive any event.
I hope it is not supposed to work that way. I wonder what could be wrong with it?
Yes thats how its supposed to work. Events go only from child to parent, it does not go from child up to grand parent. Therefore you have to emit the event from both child and inbetween component.
If you want to avoid this method you can use Event Bus:
https://medium.com/#andrejsabrickis/https-medium-com-andrejsabrickis-create-simple-eventbus-to-communicate-between-vue-js-components-cdc11cd59860
alligator.io/vuejs/global-event-bus
This is actually intentional. The reasoning is, when looking at the code for one component and you see that it's listening to an event, you can then look at the template to see where that event is coming from. If events could reach a component arbitrarily deep, it would be harder to figure out how and from where that event is being triggered.
However, Vue used to have a way of doing what you want to do, through the methods $broadcast and $dispatch, and they were eventually removed for the reasons talked about here. Here's a page from the docs which explains why, along with possible solutions, such as using a global event bus, or a centralized state management solution such as Vuex.
Vue custom events don't bubble.
The recommended way to handle ancestor/sibling communication in complex cases is using Vuex. If you have simple needs you can create Vue instance to use as event hub.
You would create a global variable:
var eventHub = new Vue(); // use a Vue instance as event hub
To emit events you would use in any component:
eventHub.$emit('myevent', 'some value');
And, to listen to that event, again, in any component, do:
eventHub.$on('myevent', (e) => {
console.log('myevent received', e)
});
Demo:
var eventHub = new Vue(); // use a Vue instance as event hub
Vue.component('parent', {
template: "#parent-tpl",
created() {
eventHub.$on('event', (e) => {
console.log('event received at parent! value:', e);
});
}
});
Vue.component('inbetween', {
template: "#inbetween-tpl"
});
Vue.component('child', {
template: "#child-tpl",
methods: {
emitEvent() {
eventHub.$emit('event', 123);
}
}
});
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<template id="parent-tpl">
<div>
<inbetween></inbetween>
</div>
</template>
<template id="inbetween-tpl">
<div>
<child></child>
</div>
</template>
<template id="child-tpl">
<div>
<h1>I'm the child</h1>
<button #click="emitEvent">Trigger EVENT</button>
</div>
</template>
<div id="app">
<p>{{ message }}</p>
<parent></parent>
</div>
Note: If creating a dedicated instance as event hub is something complicated in your environment, you can replace eventHub with this.$root (inside your components) and use your own Vue instance as hub.

Sending one time event to child components

Im struggling to implement something which I think is pretty easy.
In my Vue app I loop through a list. Each list item is a child component. Each list item has an expand/collapse button. That works fine, but I want to be able to close all open items from the parent and I don't seem to be able to get that working as I would like.
The expand/collapse is controlled via a variable called isOpen so
<div v-if="isOpen">Something here</div>
I have tried using a computed property instead of the isOpen and passing props but that issue is I think it needs to act more like an event.
Consider three open list items. If the list items are controlled by a prop, and setting it to false closes the items, when an item is reopened, the prop is still false so therefore will not work a second time. I know I could change it back on the parent, but it seems wrong.
Whats the best way to accomplish this?
If you passed an "allClosed" prop, you would need to have your child components emit events to reset it every time they opened. That seems hacky to me. I think you are right that it should be more of a parent-to-children event, which calls for an event bus.
new Vue({
el: '#app',
data: {
closeBus: new Vue(),
kids: [1, 2, 3]
},
components: {
expandableThing: {
data() {
return {
isOpen: true
}
},
props: ['bus'],
methods: {
toggle() {
this.isOpen = !this.isOpen;
}
},
created() {
this.bus.$on('close', () => this.isOpen = false);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<expandable-thing v-for="kid in kids" inline-template :bus="closeBus">
<div>
<div v-show="isOpen">My stuff!</div>
<button #click="toggle">Toggle</button>
</div>
</expandable-thing>
<button #click="closeBus.$emit('close')">Close all</button>
</div>

Vue.js passing events up to parents in components

I have a Vue app like this:
<div id="example">
<event-emitting-component #clicked="methodOnRootInstance"></event-emitting-component>
<event-emitting-component-parent></event-emitting-component-parent>
<div v-for="click in clicks">
{{ click }}
</div>
</div>
And here is the JS for it:
// Child
Vue.component('event-emitting-component', {
template: '<div class="event-emitting-component" #click="$emit(\'clicked\')">Event emitter</div>'
});
// Parent
Vue.component('event-emitting-component-parent', {
template: '<div class="event-emitting-component-parent">' +
'A custom parent!'+
'<event-emitting-component></event-emitting-component>' + // <-- Please note child component
'</div>'
});
// create a root instance
new Vue({
el: '#example',
data: {
clicks : []
},
methods : {
methodOnRootInstance : function(){
this.clicks.push('Element clicked');
}
}
})
If you want to play with it it is also here:
https://codepen.io/EightArmsHQ/pen/QgbwPG?editors=1010
When you click the top child component a click is registered on the root element. Perfect.
When the child component is nested inside a parent (the second component in the example), obviously I can't add a #clicked="methodOnRootInstance" as that method doesn't exist inside the component.
What is the best way to pass an event up through a number of nested components?
I've made a stripped back example here, but in reality some components are two or three levels deep. Is the answer (what I think it is) that inside the parent component I would have the following:
Vue.component('event-emitting-component-parent', {
template: '<div class="event-emitting-component-parent">' +
'A custom parent!'+
'<event-emitting-component #clicked="passClicked"></event-emitting-component>' + // <-- Please note child component
'</div>',
'methods': {
passClicked : function(){
this.$emit('clicked')
}
}
});
And then in the html template add the same:
<event-emitting-component-parent #clicked="methodOnRootInstance"></event-emitting-component-parent>
I know I can get it to work like this, however it doesn't seem very elegant. I've looked in the docs and there are functions such as sync although I don't think it's what I need, I'm struggling to find the correct approach.
Bonus question: can vuex help with stuff like this?
This is the type of problem vuex is designed to solve, however, before you consider adding an extra layer of complexity to your app, you may be able to get away with a simple global event bus, which is simply an empty Vue object to emit events onto, which can then be listened for by any component in your app, bypassing the parent-child chain:
const bus = new Vue({});
Vue.component('comp-1', {
template: `<div>Comp 1 <button #click="emitClick">Click</button></div>`,
methods: {
emitClick(){
bus.$emit('comp-1-click');
}
}
})
Vue.component('comp-2', {
template: `<div><comp-1></comp-1></div>`,
})
new Vue({
el: '#app',
created(){
bus.$on('comp-1-click',() => {
console.log('Comp 1 clicked');
});
}
})
Here's the JSFiddle: https://jsfiddle.net/oatLhzLp/