Call method from another vue.js component through this.$refs - javascript

I have a sibling element which i want to trigger.
i've try this solution
<b-img #click="callFileLoader"/>
<b-file type="file" ref="fileUploader"></b-file>
...
methods:{
callFileLoader () {
this.$refs.fileUploader.click()
}
}
Got: Uncaught TypeError: this.$refs.fileUploader.click is not a function
b-file documentation

After some debugging i found a way to access that input using this statement :
this.$refs.fileUploader.$el.firstChild
which is <input> element that could be clickable.
new Vue({
el: "#app",
data() {
return {
file: null,
file2: null
}
},
methods: {
callFileLoader() {
this.$refs.fileUploader.$el.firstChild.click();
}
}
});
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.0.0-rc.11/dist/bootstrap-vue.min.js"></script>
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#2.0.0-rc.11/dist/bootstrap-vue.css" />
<div id='app'>
<div>
<!-- Styled -->
<b-file v-model="file" ref="fileUploader" :state="Boolean(file)" placeholder="Choose a file..."></b-file>
<div class="mt-3">Selected file: {{file && file.name}}</div>
<button class="btn btn-primary" #click="callFileLoader">load</button>
</div>
</div>

If it is sibling component, it is not recognized in sibling component via v-ref approach.
Either try to access it via parent component or Vue root, if there is no parent component where it is nested:
this.$root.$refs.fileUploader.click()
Or use this.$root.$emit() in sibling component b-img, to trigger event, and place event listener in events of b-file component to catch emited event and trigger click
so in b-img would be:
methods:{
callFileLoader () {
this.$root.$emit('file-uploader-click');
}
}
and in b-file would be:
events:{
'file-uploader-click' : function() {
this.click();
}
}
Instead of placing events method, you try to place v-on:event-name="action" within an component:
VueTools chrome extension is very useful to check correct reference name generated by VueJs

Related

How to get DOM of a vue component without rendering it?

Suppose I have a simple Vue component like this:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
I don't want to render the component. I just want to pass the title somehow into blog-post component inside my script code, and get the DOM accordingly. For example, if I pass the title value Hello, then I expected the full DOM as <h3>Hello</h3>. I'll assign the DOM into a variable for using later.
One solution is to create a new Vue instance with only the target component, $mount it, and then get the outerHTML of its $el (root element):
Vue 2
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
const app = new Vue({
template: `<blog-post title="Hello world" />`
}).$mount()
console.log(app.$el.outerHTML)
</script>
Vue 3
In Vue 3, create an app instance, and call its mount() on a newly created <div>. The return value of mount() is the root component, which contains $el:
<script src="https://unpkg.com/vue#3.2.39/dist/vue.global.prod.js"></script>
<script>
const app = Vue.createApp({
template: `<blog-post title="Hello world" />`
})
app.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
const comp = app.mount(document.createElement('div'))
console.log(comp.$el.outerHTML)
</script>
If you want to get HTML of your component, you must to use ref attribute of parent element.
Try something like that:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div>
<button #click="logComponentBlogPost">Log Component</button>
</div>
<div v-show="false" ref="BlogPost">
<blog-post title="Hello Word"></blog-post>
</div>
</div>
<script>
let BlogPost = Vue.component('BlogPost', {
props: ['title'],
template: '<h3>{{ title }}</h3>',
});
new Vue({
el: '#app',
components: { BlogPost },
methods: {
logComponentBlogPost() {
console.log(this.$refs.BlogPost.innerHTML);
},
},
});
</script>
</body>
</html>

Toggle sidebar from Vue method?

In a <b-table> I would like to create an action on each items so I have a button:
<b-table :items="data" :fields="fields">
<template v-slot:cell(actions)="data">
<b-button v-on:click="doIt(data.index)">Do It</b-button>
</template>
</b-table>
Then I have a Form in a sidebar
<b-sidebar id="do-it-form" title="Do it" right>
...
</b-sidebar>
In my methods I would like to respond to the action:
methods: {
doIt(id) {
sidebar.form.id = id
sidebar.show().onSubmit(() => {
axio...
refresh(<b-table>)
})
}
}
Of course, this last part is not valid. On Bootstrap Vue manual I didn't find how to interact from Vue to Bootstrap components. Any clue?
You can emit an event on $root, which can be used to toggle the sidebar. The second parameter being the id of the sidebar you wish to open.
this.$root.$emit('bv::toggle::collapse', 'my-sidebar-id')
<b-collapse> and <b-sidebar> listens for the same event, which is why it says collapse in the event.
new Vue({
el: '#app',
methods: {
openSidebar() {
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.js"></script>
<div id="app">
<b-sidebar id="my-sidebar" right>
My sidebar
</b-sidebar>
<b-btn #click="openSidebar">
Open sidebar
</b-btn>
</div>
Alternatively you can bind a boolean property to the v-model on the sidebar and set the boolean to true when you want to open the sidebar.
new Vue({
el: '#app',
data() {
return {
isSidebarOpen: false
}
},
methods: {
openSidebar() {
this.isSidebarOpen = true
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.js"></script>
<div id="app">
<b-sidebar v-model="isSidebarOpen" right>
My sidebar
</b-sidebar>
<b-btn #click="openSidebar">
Open sidebar
</b-btn>
</div>
Also, you can use one of the sidebar built-in public methods show, hide or toggle. All you need is add a reference to your sidebar
<b-sidebar ref="mySidebar" id="do-it-form">
...
</b-sidebar>
and then in any of your methods where/when it's needed, you can simply call any of them
this.$refs.mysidebar.show();
this.$refs.mysidebar.hide();
this.$refs.mysidebar.toggle();
You can also just assign a boolean value to the visible prop on the b-sidebar component and toggle the boolean value as you like.
<b-sidebar ref="mySidebar" id="do-it-form" :visible="showSidebar">
...
</b-sidebar>
And toggling it:
data: {
showSidebar: false, //starts off invisible
},
methods: {
toggleSidebar(){
this.showSidebar = !this.showSidebar
}
}
This approach at first glance is not the best in an approach where you have other components updating the sidebar visiblity. This use case is for situations when all updates to the sidebar visibility are made from a central store by using a central boolean value.
e.g.
const state = {
showSidebar: null
}
const mutations: {
toggleSidebar(state, payload){
if (payload) { //payload incase you want to force a value and not just toggle
state.showSidebar = payload;
} else {
state.showSidebar = !state.showSidebar;
}
}
}
And in your components:
computed: {
showSidebar(){
return this.$store.state.showSidebar
}, //starts off invisible
},
methods: {
toggleSidebar(){
this.$store.commit("toggleSidebar");
}
}
Your updated sidebar component would look like this:
<b-sidebar ref="mySidebar" id="do-it-form" :visible="showSidebar" #change="updateSidebar">
...
</b-sidebar>
And the method:
methods: {
updateSidebar(value){
this.$store.commit("toggleSidebar", value);
}
}

VueJS: v-on:click not working on clicking the <button>

I have a Vue app configured by NuxtJS.
I have the following template and worker methods that should be called upon clicking the button. But they are not being called.
<template>
<button class="btn google-login" id="google-login" #click.native="googleLogin">
<img src="~assets/google-icon.svg" alt />
Login with Google
</button>
</template>
<script>
import firebase from "firebase";
export default {
data() {
return {
showPassword: false,
checkbox1: false
};
},
mounted: function() {
console.log(firebase.SDK_VERSION);
},
methods: {
googleLogin: function(event) {
console.log('Reached inside the function');
let googleProvider = new firebase.auth.GoogleAuthProvider();
googleProvider.addScope(
"https://www.googleapis.com/auth/contacts.readonly"
);
firebase.auth().useDeviceLanguage();
console.log(googleProvider);
}
}
};
</script>
I have the method inside the methods object. I have tried multiple solutions v-on:click, #click, #click.prevent but none seem to be working
.native event modifier is used with elements when you are trying listen any event happening in the child Component from the root Component.
For example you have a component button-counter, and your parent component need to listen to the click event happening in the button-counter component.
In your case you just neeed to trigger click event using #click="googleLogin"
Official Documentation
Read More
Sample implementation
new Vue({
el: "#app",
name: "MyApp",
components: {
'button-counter': {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
},
},
methods: {
googleLogin: function (event) {
console.log('Reached inside the function');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<div id="app">
<button class="btn google-login" id="google-login" #click="googleLogin">
Login with Google
</button>
<button-counter #click.native="googleLogin" />
</div>
You need to trigger the click event this way.
#click="googleLogin"

child component emit an custom event, but parent component's listener not triggered

I'm registered a child component in Vue, the child will emit an custom event, but the parent component's listener not triggered.
<template id="add-item-template">
<div class="input-group">
<input #keyup.enter="addItem" v-model="newItem" >
<span class="input-group-btn">
<button #click="addItem" type="button">Add!</button>
</span>
</div>
</template>
<div id="app" class="container">
<h2>{{ title }}</h2>
<add-item-component v-on:itemAdd="addAItem"></add-item-component>
</div>
Vue.component('add-item-component', {
template: '#add-item-template',
data: function () {
return {
newItem: ''
};
},
methods: {
addItem() {
this.$emit('itemAdd');
console.log("itemAdd In add-item-component");
}
}
});
new Vue({
el: '#app',
data: {
title: 'Welcome to Vue'
},
methods: {
addAItem: function () {
console.log("In #app, addAItem()!");
}
}
});
The "itemAdd In add-item-component" log show in console, but "In #app, addAItem()!" log not, the #app's method addAItem not invoked.
The problem is that custom events should not be named with camelCase. If you look at the error message in the console, it tells you:
Event "itemadd" is emitted in component but the handler is registered for "itemAdd"
The component is emitting a lowercase version even though you've used camelCase to name it. Renaming to all lowercase or kebab-case will fix it.
In the parent template:
<add-item-component #itemadd="addAItem">
Emitting from the child:
this.$emit('itemadd');
This is discussed a bit with Evan You (Vue creator) here

Call a Vue.js component method from outside the component

Let's say I have a main Vue instance that has child components. Is there a way of calling a method belonging to one of these components from outside the Vue instance entirely?
Here is an example:
var vm = new Vue({
el: '#app',
components: {
'my-component': {
template: '#my-template',
data: function() {
return {
count: 1,
};
},
methods: {
increaseCount: function() {
this.count++;
}
}
},
}
});
$('#external-button').click(function()
{
vm['my-component'].increaseCount(); // This doesn't work
});
<script src="http://vuejs.org/js/vue.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app">
<my-component></my-component>
<br>
<button id="external-button">External Button</button>
</div>
<template id="my-template">
<div style="border: 1px solid; padding: 5px;">
<p>A counter: {{ count }}</p>
<button #click="increaseCount">Internal Button</button>
</div>
</template>
So when I click the internal button, the increaseCount() method is bound to its click event so it gets called. There is no way to bind the event to the external button, whose click event I am listening for with jQuery, so I'll need some other way to call increaseCount.
EDIT
It seems this works:
vm.$children[0].increaseCount();
However, this is not a good solution because I am referencing the component by its index in the children array, and with many components this is unlikely to stay constant and the code is less readable.
In the end I opted for using Vue's ref directive. This allows a component to be referenced from the parent for direct access.
E.g.
Have a component registered on my parent instance:
var vm = new Vue({
el: '#app',
components: { 'my-component': myComponent }
});
Render the component in template/html with a reference:
<my-component ref="foo"></my-component>
Now, elsewhere I can access the component externally
<script>
vm.$refs.foo.doSomething(); //assuming my component has a doSomething() method
</script>
See this fiddle for an example: https://jsfiddle.net/0zefx8o6/
(old example using Vue 1: https://jsfiddle.net/6v7y6msr/)
Edit for Vue3 - Composition API
The child-component has to return the function in setup you want to use in the parent-component otherwise the function is not available to the parent.
Note: <sript setup> doc is not affacted, because it provides all the functions and variables to the template by default.
You can set ref for child components then in parent can call via $refs:
Add ref to child component:
<my-component ref="childref"></my-component>
Add click event to parent:
<button id="external-button" #click="$refs.childref.increaseCount()">External Button</button>
var vm = new Vue({
el: '#app',
components: {
'my-component': {
template: '#my-template',
data: function() {
return {
count: 1,
};
},
methods: {
increaseCount: function() {
this.count++;
}
}
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component ref="childref"></my-component>
<button id="external-button" #click="$refs.childref.increaseCount()">External Button</button>
</div>
<template id="my-template">
<div style="border: 1px solid; padding: 2px;" ref="childref">
<p>A counter: {{ count }}</p>
<button #click="increaseCount">Internal Button</button>
</div>
</template>
For Vue2 this applies:
var bus = new Vue()
// in component A's method
bus.$emit('id-selected', 1)
// in component B's created hook
bus.$on('id-selected', function (id) {
// ...
})
See here for the Vue docs.
And here is more detail on how to set up this event bus exactly.
If you'd like more info on when to use properties, events and/ or centralized state management see this article.
See below comment of Thomas regarding Vue 3.
You can use Vue event system
vm.$broadcast('event-name', args)
and
vm.$on('event-name', function())
Here is the fiddle:
http://jsfiddle.net/hfalucas/wc1gg5v4/59/
A slightly different (simpler) version of the accepted answer:
Have a component registered on the parent instance:
export default {
components: { 'my-component': myComponent }
}
Render the component in template/html with a reference:
<my-component ref="foo"></my-component>
Access the component method:
<script>
this.$refs.foo.doSomething();
</script>
Say you have a child_method() in the child component:
export default {
methods: {
child_method () {
console.log('I got clicked')
}
}
}
Now you want to execute the child_method from parent component:
<template>
<div>
<button #click="exec">Execute child component</button>
<child-cmp ref="child"></child_cmp> <!-- note the ref="child" here -->
</div>
</template>
export default {
methods: {
exec () { //accessing the child component instance through $refs
this.$refs.child.child_method() //execute the method belongs to the child component
}
}
}
If you want to execute a parent component method from child component:
this.$parent.name_of_method()
NOTE: It is not recommended to access the child and parent component like this.
Instead as best practice use Props & Events for parent-child communication.
If you want communication between components surely use vuex or event bus
Please read this very helpful article
This is a simple way to access a component's methods from other component
// This is external shared (reusable) component, so you can call its methods from other components
export default {
name: 'SharedBase',
methods: {
fetchLocalData: function(module, page){
// .....fetches some data
return { jsonData }
}
}
}
// This is your component where you can call SharedBased component's method(s)
import SharedBase from '[your path to component]';
var sections = [];
export default {
name: 'History',
created: function(){
this.sections = SharedBase.methods['fetchLocalData']('intro', 'history');
}
}
Using Vue 3:
const app = createApp({})
// register an options object
app.component('my-component', {
/* ... */
})
....
// retrieve a registered component
const MyComponent = app.component('my-component')
MyComponent.methods.greet();
https://v3.vuejs.org/api/application-api.html#component
Here is a simple one
this.$children[indexOfComponent].childsMethodName();
I am not sure is it the right way but this one works for me.
First import the component which contains the method you want to call in your component
import myComponent from './MyComponent'
and then call any method of MyCompenent
myComponent.methods.doSomething()
Declare your function in a component like this:
export default {
mounted () {
this.$root.$on('component1', () => {
// do your logic here :D
});
}
};
and call it from any page like this:
this.$root.$emit("component1");
If you're using Vue 3 with <script setup> sugar, note that internal bindings of a component are closed (not visible from outside the component) and you must use defineExpose(see docs) to make them visible from outside. Something like this:
<script setup lang="ts">
const method1 = () => { ... };
const method2 = () => { ... };
defineExpose({
method1,
method2,
});
</script>
Since
Components using are closed by default
Sometimes you want to keep these things contained within your component. Depending on DOM state (the elements you're listening on must exist in DOM when your Vue component is instantiated), you can listen to events on elements outside of your component from within your Vue component. Let's say there is an element outside of your component, and when the user clicks it, you want your component to respond.
In html you have:
Launch the component
...
<my-component></my-component>
In your Vue component:
methods() {
doSomething() {
// do something
}
},
created() {
document.getElementById('outsideLink').addEventListener('click', evt =>
{
this.doSomething();
});
}
I have used a very simple solution. I have included a HTML element, that calls the method, in my Vue Component that I select, using Vanilla JS, and I trigger click!
In the Vue Component, I have included something like the following:
<span data-id="btnReload" #click="fetchTaskList()"><i class="fa fa-refresh"></i></span>
That I use using Vanilla JS:
const btnReload = document.querySelector('[data-id="btnReload"]');
btnReload.click();

Categories