Calling component method in template while v-foring the component - vuejs2

I'm using vuejs2. I want to create a method in my component and then call it to bind a class to it, but I can't get it to work. This is what I want to accomplish more or less, where getMachineClass() ideally would be a method in my machine component.
<machine
v-for="item in machinesList"
v-bind:key="item.id"
v-bind:machine="item"
v-bind:class="item.getMachineClass()">
</machine>
I know I can just put the method in my vue instance and then call it like this:
v-bind:class="getMachineClass(item)
I would like the method to be just in the component though. What can I do about it?
Machine component:
Vue.component('machine', {
props: ['machine'],
template: '#machine',
data: function () {
return {
translations: translations,
options: options
}
},
mounted: ...
});

You can just bind the class on the root element of your machine component
//machine component template
<script type="text/x-template" id="machine">
<div v-bind:class="getMachineClass(machine)">
//...
</div>
</script>
Since you are receiving the iterated item as a machine prop, pass it to the getMachineClass method

Related

Cannot read properties of undefined (reading '$refs') vue js

Getting the Error Cannot read properties of undefined (reading '$refs') though I'm having a reference in the template. Does it mean I must use Vue mounted hook ?
<div class="input-wrapper">
<input type="text" id="phone" placeholder="(555) 555-5555" ref="input"/>
</div>
<script>
this.$refs.input.addEventListener('input', function () {
// some code
});
</script>
Inside root of <script> of a Vue component, in both Vue 2 and Vue 3, this is undefined:
<script>
console.log(this) // undefined
</script>
See it here.
Vue template refs can only be accessed inside any hook or method happening after the component has been mounted and before it is unmounted.
Which means the earliest you can reference this.$refs is inside mounted. And the latest is in beforeUnmount. And you can also access them in any hook or method happening between those two moments.
Considering you're attempting to add an event listener to a HTMLInputElement, I'd recommend using the v-on directive, which automatically adds the event listener on mount and removes it on unmount.
In your case:
<template>
<div class="input-wrapper">
<input
type="text"
id="phone"
placeholder="(555) 555-5555"
#input="myFn" />
</div>
</template>
<script>
export default {
methods: {
myFn(event) {
console.log(event)
}
}
}
</script>
As a side note, you should know that a regular function's this doesn't have access to the component context, unless it's an arrow function:
export default {
mounted() {
this.$refs.input.addEventListener('input', function() {
/*
* Here `this` is the context of the current function, you can't
* access methods, computed, data or props of the component.
* You'd need to make it an arrow function to access the component scope
*/
})
}
}
Whereas in any method (e.g: the above myFn), this is the component context, which has access to all component members.

Calling function inside child component without an event?

Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.

Pass data from blade to vue and keep parent-child in sync?

I know that in Vue parents should update the children through props and children should update their parents through events.
Assume this is my parent component .vue file:
<template>
<div>
<my-child-component :category="category"></my-child-component>
</div>
</template>
<script>
export default {
data: {
return {
category: 'Test'
}
}
}
</script>
When I update the category data in this component, it will also update the category props in my-child-component.
Now, when I want to use Vue in Laravel, I usually use an inline template and pass the value from the blade directly to my components (as for example also suggested at https://stackoverflow.com/a/49299066/2311074).
So the above example my my-parent-component.blade.php could look like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component inline-template>
<my-child-component :category="{{ $category }}"></my-child-component>
</my-parent-component>
But now my-parent-component is not aware about the data of category. Basically only the child knows the category and there is no communication between parent and child about it.
How can I pass the data from blade without breaking the parent and child communication?
I just had to pass the category to the inline-template component through props like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component :initcategory="{$category}}" inline-template>
<my-child-component v-model="category"></my-child-component>
</my-parent-component>
In my-parent-component I had to set the props and initialize is using the create method:
export default {
props: {
initcategory: '',
},
data() {
return {
category: '',
};
},
created(){
this.category = this.initcategory;
}
}
Now my my-parent-component is fully aware of the category and it can communicate to the child using props and $emit as usual.
Your reference to this answer is different altogether from what you are looking for!
He's binding the :userId prop of the example component but not the parent component or in simple words: Any template using the example vue can either pass a string prop or bind :userId prop to a string variable. Following is similar:
<example :userId="{{ Auth::user()->id }}"></example>
OR
<example :userId="'some test string'"></example>
So you should rather assign {{ $category }} to a data variable but rather binds to a child component prop which will have no effect on the parent.
In the following snippet you're only binding the string but rather a data key:
<my-child-component :category="{{ $category }}"></my-child-component>
Update
See the following example which will change the h1 title after 3 seconds
// HelloWorld.vue
<template>
<app-name :name="appName" #appNameChanged="appName = $event"></app-name>
</template>
<script>
export default {
props: ['name'],
data() {
return {
appName: null
}
},
mounted() {
// NOTE: since Strings are immutable and thus will assign the value while objects and arrays are copied by reference
// the following is just for the purpose of understanding how binding works
this.appName = this.name;
}
}
</script>
The template which renders the app title or you can say the child component
// AppName.vue
<template>
<h1>{{ name }}</h1>
</template>
<script>
export default {
props: ['name'],
mounted() {
setTimeout(() => {
this.$emit('appNameChanged', 'Change App')
}, 3000);
}
}
</script>
And here's how it is being used in the welcome.blade.php
<div id="app">
<hello-world :name="'Laravel App'"></hello-world>
</div>

Using v-model and refs in a slot in Vue2

I have a component that takes a main <slot> from a form that is generated elsewhere in my application. I'm trying to use v-model on the form inputs but my vue component just spits out a warning about the properties not being defined, when in fact they are.
I admit it's a weird way of doing things, but it seems to be the easiest way for me to do this since my form is being generated by Symfony.
html:
<my-component>
<input ref="start" v-model="start"/>
</my-component>
my component:
<script>
export default {
data() {
start: null
},
mounted() {
console.log(this.$refs) // === {}; expected {"start":{...}}
}
}
</script>
<template>
<div>
<slot/>
... other stuff here
</div>
</template>
console log:
Property or method "start" is not defined on the instance but referenced during render
I cannot use $refs or v-model in the html. Am I doing something wrong? Or is this just not possible.
If you declare v-model="start" in the parent then it belongs to the parent and needs to be declared there. It looks like instead you declare it in the component instead as null.
If you reorder things it should work as you expect:
Parent:
<parent>
<input v-model="start" :start="start"/>
</parent>
<script>
export default {
data() {
start: null // Important to define start here if it's used in this component's html
}
}
</script>
Component:
<template>
<div>
<slot/>
... other stuff here
</div>
</template>
<script>
export default {
props: ['start'], // Receive the prop from the parent
data() {
},
mounted () {
console.log(this.start) // Should echo the value of the prop from the parent
}
}
</script>

Vuejs vue-nav-tabs change title of tabs [duplicate]

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();