Vue: Data 'undefind' when component render - vue.js

I would like to set a class to one of my elements when current language I equal to element id. But when method is fired to do this check then I have log "undefined".
Why? How to set that class properly?
Is data object loaded latter then component render?
<template>
<div>
<div class="star-box">
<div class="head">
<img :src="'images/flags/en.png'"
id="en"
class="student-img"
v-bind:class="{'activeLanguage': checkActiveLanguage('en')}"
alt=""
>
</div>
<div class="body">
<h5 class="heading">English</h5>
</div>
</div>
<div class="star-box">
<div class="head">
<img :src="'images/flags/de.png'"
id="de"
class="student-img"
v-bind:class="{'activeLanguage': checkActiveLanguage('de')}"
alt=""
>
</div>
<div class="body">
<h5 class="heading">Deutsch</h5>
</div>
</div>
</div>
</template>
JS
<script>
import {mapActions,mapGetters} from 'vuex';
export default {
name: 'Language',
data() {
return {
language: ''
}
},
methods: {
checkActiveLanguage: (lang)=> {
console.log(this.language);
if(lang==this.language) return true;
},
...mapGetters(['getCurrentLanguage']),
},
beforeMount(){
this.language = this.getCurrentLanguage();
}
}
</script>

Related

Vue 3: How to use the render function to render different components that themselves render different components with slots, events, etc?

The title is basically my question. But the following code should make clearer what my goal is.
I have a version of this:
// AppItem.vue
<script>
import { h } from 'vue'
import { AppItem1 } from './item-1';
import { AppItem2 } from './item-2';
import { AppItem3 } from './item-3';
const components = {
AppItem1,
AppItem2,
AppItem3,
};
export default {
props: {
level: Number
},
render() {
return h(
components[`AppItem${this.level}`],
{
...this.$attrs,
...this.$props,
class: this.$attrs.class + ` AppItem--${this.level}`,
},
this.$slots
)
}
}
</script>
// AppItem1.vue
<template>
<AppBlock class="AppItem--1">
<slot name="header">
#1 - <slot name="header"></slot>
</slot>
<slot></slot>
</AppBlock>
</template>
// AppItem2.vue
<template>
<AppBlock class="AppItem--2">
<template #header>
#2 - <slot name="header"></slot>
</template>
<slot></slot>
</AppBlock>
</template>
// AppBlock.vue
<template>
<div class="AppBlock">
<div class="AppBlock__header">
<slot name="header"></slot>
</div>
<div class="AppBlock__body">
<slot></slot>
</div>
</div>
</template>
And my goal would be to use <AppItem> like...
<AppItem level="1">
<template #header>
Animal
</template>
<AppItem level="2">
<template #header>
Gorilla
</template>
<p>The gorilla is an animal...</p>
</AppItem>
<AppItem level="2">
<template #header>
Chimpanzee
</template>
<p>The Chimpanzee is an animal...</p>
</AppItem>
</AppItem>
...and have it render like...
<div class="AppBlock AppItem AppItem--1">
<div class="AppBlock__header">
Animal
</div>
<div class="AppBlock__body">
<div class="AppBlock AppItem AppItem--2">
<div class="AppBlock__header">
Gorilla
</div>
<div class="AppBlock__body">
<p>The gorilla is an animal...</p>
</div>
</div>
<div class="AppBlock AppItem AppItem--2">
<div class="AppBlock__header">
Chimpanzee
</div>
<div class="AppBlock__body">
<p>The Chimpanzee is an animal...</p>
</div>
</div>
</div>
</div>
Why does it not work? What I'm I misunderstaning?
The right way to get the AppItem--N component by name is to use the resolveComponent() function:
const appItem = resolveComponent(`AppItem${props.level}`);
I also fixed a couple of other small problems and had to rewrite the <setup script> to the setup() function. Now it works.
Playground
const { createApp, h, resolveComponent } = Vue;
const AppBlock = { template: '#appblock' }
const AppItem1 = { components: { AppBlock }, template: '#appitem1' }
const AppItem2 = { components: { AppBlock }, template: '#appitem2' }
const AppItem = {
components: {
AppItem1, AppItem2, AppBlock
},
props: {
level: Number
},
setup(props, { attrs, slots, emit, expose } ) {
const appItem = resolveComponent(`AppItem${props.level}`);
return () =>
h(appItem, {
...attrs,
...props,
class: (attrs.class ? attrs.class : '') + " AppItem--" + props.level,
}, slots);
}
}
const App = { components: { AppItem } }
const app = createApp(App)
app.mount('#app')
#app { line-height: 1; }
[v-cloak] { display: none; }
<div id="app">
<app-item :level="1">
<template #header>
<h4>Animal</h4>
</template>
<app-item :level="2">
<template #header>
Gorilla
</template>
<p>The gorilla is an animal...</p>
</app-item>
<app-item :level="2">
<template #header>
Chimpanzee
</template>
<p>The Chimpanzee is an animal...</p>
</app-item>
</app-item>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="appitem1">
<app-block class="AppItem">
<slot name="header">
#1 - <slot name="header"></slot>
</slot>
<slot></slot>
</app-block>
</script>
<script type="text/x-template" id="appitem2">
<app-block class="AppItem">
<template #header>
#2 - <slot name="header"></slot>
</template>
<slot></slot>
</app-block>
</script>
<script type="text/x-template" id="appblock">
<div class="AppBlock">
<div class="AppBlock__header">
<slot name="header"></slot>
</div>
<div class="AppBlock__body">
<slot></slot>
</div>
</div>
</script>

I made the form a component, it disappeared with Vue.js

overview
Currently, the created new.vue and edit.vue have similar form parts, so I would like to make them common and componentized.
Therefore, I would like to create a new form.vue and display the page in the form of calling it.
However, when I made it into a component, the page disappeared.
(There is no such description in the log, and there is no error display in Console)
I think the data transfer isn't working, but I'm not sure where to fix it.
I would appreciate it if you could tell me how to fix it.
Original code before componentization
New.vue
<template>
<main>
<form>
<section>
<div>
<div>
<fieldset>
<div class="form-row">
<div class="form-group col-3">
<label>タイトル</label>
<input v-model="latest_information.title" type="text">
</div>
<div class="form-group col-3">
<label>詳細</label>
<input v-model="latest_information.detail" type="text">
</div>
</div>
</fieldset>
</div>
</div>
</section>
<div class="btn-container d-flex justify-content-center">
<button class="button-square btn-send" type="button" #click="createLatestInformation">保存する</button>
</div>
</form>
</main>
</template>
<script>
export default {
data() {
return {
latest_information: {
title: '',
detail: '',
},
}
},
methods: {
createLatestInformation() {
this.$loading.load(this.$auth.api.post('admin/latest_informations/', {
latest_information: this.latest_information
})
.then(response => {
this.$router.push({name: 'AdminLatestInformationIndex'})
}))}
},
}
</script>
Code after componentization (not behaving well)
New.vue
<template>
<form :latest_information="latest_information" #click="createLatestInformation"></form>
</template>
<script>
import Form from './Form.vue';
export default {
components:{
Form
},
data() {
return {
latest_information: {
title: '',
detail: '',
},
}
},
methods: {
createLatestInformation() {
this.$loading.load(this.$auth.api.post('admin/latest_informations/', {
latest_information: this.latest_information
})
.then(response => {
this.$router.push({name: 'AdminLatestInformationIndex'})
}))}
},
}
</script>
Form.vue
<template>
<main>
<form>
<section>
<div>
<div>
<fieldset>
<div class="form-row">
<div class="form-group col-3">
<label>タイトル</label>
<input v-model="latest_information.title" type="text">
</div>
<div class="form-group col-3">
<label>詳細</label>
<input v-model="latest_information.detail" type="text">
</div>
</div>
</fieldset>
</div>
</div>
</section>
<div class="btn-container d-flex justify-content-center">
<button class="button-square btn-send" type="button" #click="$emit('click')">保存する</button>
</div>
</form>
</main>
</template>
<script>
export default {
props: {
latest_information: {
title: '',
detail: '',
},
}
}
</script>
<style>
</style>
Environment
rails 6
vue#2.6.10
form was a reserved word.Changed to tform.
<template>
<tform :latest_information="latest_information" #click="createLatestInformation"></form>
</template>
<script>
import Form from './Form.vue';
export default {
components:{
Tform: Form
},

Vue.JS: Communication with component

I have two component Header and AddUser component. Inside Header component I have a button that has function to show modal. And the modal is inside AddUser component like below.
Header.vue
<template>
<header>
<h2>{{title}}</h2>
<p>{{description}}</p>
<div class="text-center btn-class">
<button #click="showModal = true">Add Vehicle</button>
</div>
</header>
</template>
<script>
export default{
name: 'Header',
props: {
title: {
type: String,
default: 'Title!!',
},
description: {
type: String,
default: 'Description!!',
}
}
}
</script>
AddUser.vue
<template>
<div>
<transition name="fade" appear>
<div class="modal-overlay" v-if="showModal" #click="showModal = false"></div>
</transition>
<transition name="slide" appear>
<div class="modal" v-if="showModal">
<h3>Enter your vehicle detail</h3>
<div class="body">
<form #submit="onSubmit">
<div class="form-group">
<input type="text" v-model="name" name="name" class="form-control" placeholder="Enter your name">
</div>
<br><br>
<div class="form-btn form-group text-center">
<Button type="submit" text="Add" color="#2BA0A3" />
</div>
</form>
</div>
</div>
</transition>
</div>
</template>
<script>
import Button from './Button'
export default {
name: 'AddUser',
data(){
return {
showModal: false,
name: ''
}
},
components: {
Button
},
methods: {
onSubmit(e){
e.preventDefault()
if(!this.name){
alert('Please enter name.')
}
},
}
}
</script>
And this is my App.vue
<template>
<div class="container">
<Header />
<AddUser />
</div>
</template>
<script>
import Header from './components/Header'
import AddUser from './components/AddUser'
export default {
name: 'App',
components: {
Header,
AddUser,
}
}
</script>
The code works perfectly if I put button inside AddUser.vue but I want to keep the button on Header.vue and want it work also. How can I make it both component to communicate?
Move your showModal variable to your App.vue and pass it to your AddUser component as a prop. Inside your Header and AddUser components you will need to update the showModal value by emitting a show-modal-event with the value true or false depending on if you want to show or hide the modal. Listen for the event in App.vue and update it's local data showModal value with the $events value.
Here is an example. ** FYI this code isn't tested so there might be typos.
Header.vue
<template>
<header>
<h2>{{title}}</h2>
<p>{{description}}</p>
<div class="text-center btn-class">
<button #click="$emit('show-modal-event', true)">Add Vehicle</button>
</div>
</header>
</template>
<script>
export default{
name: 'Header',
props: {
title: {
type: String,
default: 'Title!!',
},
description: {
type: String,
default: 'Description!!',
}
}
}
</script>
AddUser.vue
<template>
<div>
<transition name="fade" appear>
<div class="modal-overlay" v-if="showModal" #click="$emit('show-modal-event', false)"></div>
</transition>
<transition name="slide" appear>
<div class="modal" v-if="showModal">
<h3>Enter your vehicle detail</h3>
<div class="body">
<form #submit="onSubmit">
<div class="form-group">
<input type="text" v-model="name" name="name" class="form-control" placeholder="Enter your name">
</div>
<br><br>
<div class="form-btn form-group text-center">
<Button type="submit" text="Add" color="#2BA0A3" />
</div>
</form>
</div>
</div>
</transition>
</div>
</template>
<script>
import Button from './Button'
export default {
name: 'AddUser',
data(){
return {
name: ''
}
},
props: {
showModal: {
type: Boolean,
default: false
}
},
components: {
Button
},
methods: {
onSubmit(e){
e.preventDefault()
if(!this.name){
alert('Please enter name.')
}
},
}
}
</script>
App.vue
<template>
<div class="container">
<Header #show-modal-event="showModal=$event" />
<AddUser #show-modal-event="showModal=$event" :showModal="showModal" />
</div>
</template>
<script>
import Header from './components/Header'
import AddUser from './components/AddUser'
export default {
name: 'App',
components: {
Header,
AddUser,
},
data(){
return {
showModal: false,
}
},
}
</script>

Passing events between Vue parent and child

I am trying to emit an event from a child and listen for it on the parent, but I am always getting errors like Invalid handler for event "new-tab": got undefined.
My app:
<div class="ibox" id="app">
<div class="ibox-content">
<h2>Table</h2>
<div class="details">
<table-tabs
v-on:new-tab="newTab"
v-on:close-tab="closeTab"
ref="tableTabs"
name="table-tabs"
></table-tabs>
</div>
</div>
</div>
TableTabs component:
<template>
<div>
<b-card no-body>
<b-tabs card>
<b-tab active>
<template slot="title">
<i class="fa fa-tablet-alt"></i> Orders
</template>
<orders-table
name="orders-table"
></orders-table>
</b-tab>
<b-tab v-for="order in tabs" :key="i">
<template slot="title">
<div>{{ order.name }}</div>
<b-button type="button" class="close float-right" aria-label="Close" #click="closeTab(order.id)">
<span aria-hidden="true">×</span>
</b-button>
</template>
<items-table
name="items-table"
:api-url="'/api/items/' + order.id"
></items-table>
</b-tab>
</b-tabs>
</b-card>
</div>
</template>
<script>
export default {
name: 'table-tabs',
data() {
return {
tabs: [],
}
},
methods: {
closeTab(id) {
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i].id === id) {
this.tabs.splice(i, 1);
}
}
},
newTab(item) {
this.tabs.push(item);
}
}
}
</script>
OrdersTable component:
<template>
<div>
<vuetable ref="vuetable"
:api-url="apiUrl"
:fields="fields"
pagination-path="pagination"
#vuetable:pagination-data="onPaginationData"
>
<div slot="name-slot" slot-scope="{ rowData }">
<a href="#" class="float-left" #click="newTab(rowData.order)">
{{ rowData.order.name }}
<span class="fa fa-search-plus"></span>
</a>
</div>
</vuetable>
<div class="row">
<div class="col-md-6">
<vuetable-pagination-info
ref="paginationInfo"
></vuetable-pagination-info>
</div>
<div class="col-md-6">
<vuetable-pagination-bootstrap
ref="pagination"
class="pull-right"
#vuetable-pagination:change-page="onChangePage"
></vuetable-pagination-bootstrap>
</div>
</div>
</div>
</template>
<script>
import Vuetable from 'vuetable-2/src/components/Vuetable';
import VuetablePaginationBootstrap from '../VuetablePaginationBootstrap';
import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo';
import TableFieldDef from './table-field-def';
export default {
name: 'orders-table',
components: {
Vuetable,
VuetablePaginationBootstrap,
VuetablePaginationInfo
},
data() {
return {
apiUrl: '/api/orders?include=items',
fields: TableFieldDef,
}
},
methods: {
newTab(order) {
this.$emit('new-tab', order);
}
}
}
</script>
Why does this.$emit('new-tab', order) always result in the handler newTab being undefined.
Vue 2.5.21
The parent in this case is the TableTabs component. If the parent needs to listen to events emitted by a child component then it needs to add the event listener to the child component, which is the OrdersTable component in this case. So instead of this ..
<table-tabs
v-on:new-tab="newTab"
v-on:close-tab="closeTab"
ref="tableTabs"
name="table-tabs"
></table-tabs>
You should do this (inside the TableTabs component) ..
<orders-table
name="orders-table"
v-on:new-tab="newTab"
></orders-table>

[Vue warn]: Error in render: “TypeError: Cannot read property ‘name’ of undefined”

I get the error
TypeError: Cannot read property ‘name’ of undefined”
if I go to deep in the object.
Timeline.vue
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card card-default">
<div class="card-header">Timeline</div>
<div class="card-body">
<post v-for="post in posts" :key="post.id"></post>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Post from './Post.vue'
export default {
data () {
return {
posts: []
}
},
components: {
Post
},
mounted() {
this.$http.get('/posts').then((response) => {
console.log(response.body)
this.posts =response.body
})
}
}
</script>
post.vue
<template>
<div class="media">
<div class="media-left">
</div>
<div class="media-body">
<strong>test</strong>
<strong>{{ post.user.name }} </strong>
</div>
</div>
</template>
<script>
export default {
props: [
'post'
]
}
</script>
Then why I get the error?
I suppose some problem with {{ post.user.name }}.
You need to pass the post prop in the template like so. otherwise it's undefined in the child component (post).
<div class="card-body">
<post
v-for="post in posts"
:key="post.id"
:post="post">
</post>
</div>