Trigger store action from outside vue app - vue.js

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!

Related

How to display stub component when component is not found in vue

I am trying to catch situation, when component is not found, ie:
{
template: '<some-unknown-component></some-unknown-component>'
}
At that moment, Vue warns us with unknown custom element: <some-unknown-component>...
I would like to step in when some-unknown-component is not found and then use another component instead, like stub-component:
{
name: 'stub-component',
props: ['componentName'],
template: '<p>component ${componentName} does not exists, click here to create...</p>'
}
UPDATE: I am looking for solution without changing the template itself, so no v-if and component added.
Vue exposes a global error and warning handler. I managed to get a working solution by using the global warnHandler. I don't know if it is exactly what you are looking for, but it may be a good starting point. See the working snippet (I think it is quite self explanatory).
Vue.config.warnHandler = function (err, vm, info) {
if (err.includes("Unknown custom element:")) {
let componentName = err.match(/<.*>/g)[0].slice(1, -1)
vm.$options.components[componentName] = Vue.component('stub-component', {
props: ['componentName'],
template: `<p>component "${componentName}" does not exists, click here to create...</p>`,
});
vm.$forceUpdate()
} else {
console.warn(err)
}
};
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<unknown-component></unknown-component>
</div>
Vue stores the details of all the registered components in the $options.component property of the Vue instance.
So, you can check for the component availability using this.$options.component and if the component is present then load the component otherwise load the other component.
In the below example, suppose you have two different components and you want to load them on the availability, then you can create a computed property on the basis of it, load the component as needed.
var CustomComponent = Vue.extend({ template: '<h2>A custom Component</h2>' });
var AnotherComponent = Vue.extend({ template: '<h2>Custom component does not exist.</h2>' });
new Vue({
el: "#app",
components: {
CustomComponent,
AnotherComponent
},
computed: {
componentAvailable () {
return this.$options.components.CustomComponent
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="componentAvailable">
<custom-component />
</div>
<div v-else>
<another-component />
</div>
</div>

VueJs: bind `v-on` on a custom component to replace an existing one

In order to ease the styling of my page, I'd like to create a bunch of mini components like, and exploit how attributes are merged in VueJs. So for example, here is a minimal js file also hosted on this JSFiddle:
Vue.component('my-button', {
template: '<button style="font-size:20pt;"><slot></slot></button>'
})
var app = new Vue({
el: "#app",
data: {
message: "world",
},
methods: {
sayHello: function () {
alert("Hello");
}
}
})
and then in my html I just want to use <my-button> instead of button:
<div id="app">
Hello {{message}} <my-button #click="sayHello" style="color:red;">Style works, but not click</my-button> <button v-on:click="sayHello" style="color:red;">Both works</button>
</div>
Unfortunately, it seems that attributes are merged, but not listeners, so it means that I can't do v-on:click on my new button... Any way to make it possible?
Thanks!
-- EDIT --
I saw the proposition of Boussadjra Brahim of using .native, and it works, but then I found this link that explains why it's not a great practice and how to use v-on="$listeners" to map all listeners to a specific sub-button. However, I tried, to just change my template with:
template: `<button style="font-size:20pt;" v-on="$listeners"><slot></slot></button>`,
but I get an error:
Vue warn: Property or method "$listeners" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option."
Here is the JSFiddle.
Your fiddle didn't work because you were using an old version of Vue, $listeners was added in Vue 2.4.0.
Here's a demo:
Vue.component('my-button', {
template: '<button style="color: red" v-on="$listeners"><slot/></button>'
})
new Vue({
el: '#app',
methods: {
sayHello() {
alert('Hello')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-button #click="sayHello">Custom Button</my-button>
<button #click="sayHello">Ordinary Button</button>
</div>

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

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.

Vuejs modal component cannot reference method

I have a modal dialog which is trying to use a method on the Vue app instance but getting the error
app.js:32117 [Vue warn]: Property or method "calcFees" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
The app declaration
Vue.component('sale', require('./components/Sale.vue'));
const app = new Vue({
el: '#app',
data: {
showModal: false
},
methods: {
calcFees: function (event) {
alert('GOOD');
}
}
});
Sale.vue component minimised for now
<template name="sale">
<input type="text" placeholder="Sale Price" class="form-control" #blur="calcFees">
</template>
The sale component is simply included in the main page here
<sale v-if="showModal"></sale>
The modal dialog works fine, displays the above text input however the above error is shown in the console and the blur event doesn't call the method.
It seems it has something to do with the component template, because i tested the blur event successfully by putting a text input in the main blade page directly.
Any ideas why it doesn't work in this way? I saw a comment somewhere about something to do with it being a <template> but it didn't explain how to fix.
Components cannot access methods declared in other components or the root Vue directly.
The problem with this code is that the calcFees method is declared in the root Vue, but you are trying to call it from the Sale.vue component.
There are several ways to make this work. One is you can move calcFees to the component. Another is you can $emit an event to the parent with whatever it needs to use in calcFees.
Sale.vue
<template name="sale">
<input type="text" v-model="price" placeholder="Sale Price" class="form-control" #blur="onSale">
</template>
<script>
export default {
data(){
return {
price: null
}
},
methods: {
onSale(){
this.$emit('sale', this.price)
}
}
}
</script>
Vue
<sale v-if="showModal" #sale="calcFees"></sale>
const app = new Vue({
el: '#app',
data: {
showModal: false
},
methods: {
calcFees: function (price) {
alert(price);
}
}
});

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/