Very strange problem with vuetify datatable / props is not defined - vue.js

I am using version 1.5.6 of Vuetify (upset on a Laravel 5.8 backend and VueJs 2.5.17) and put one of the DatatableComponent examples (https://vuetifyjs.com/en/components/data-tables) from the documentation into my app adopting it to my requirements.
However, I did not change a lot of things but when it runs in my app, I get the following error every time the datatable is rendered:
Property or method "props" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties. in TransactionComponent
When I try exactly the same code on Codepen it works without any problems:
https://codepen.io/anon/pen/Rdjjgx
On my local app I have the following structure (the only difference to the example above is, that the component itself is loaded via VueJs router not via template):
TransactionComponent.vue:
<template>
<v-card>
<v-card-title>
Transaktionen
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="search"
label="Suche..."
single-line
hide-details
></v-text-field>
</v-card-title>
<v-data-table
:headers="headers"
:items="transactions"
:search="search">
<template v-slot:items="props">
<td>{{ props.item.date }}</td>
<td class="text-xs-right">{{ props.item.type }}</td>
<td class="text-xs-right">{{ props.item.remark }}</td>
<td class="text-xs-right">{{ props.item.plane }}</td>
<td class="text-xs-right" v-bind:class="{'color':(props.item.fee > 0 ? '#0F0' : '#F00')}">{{ props.item.fee }}</td>
</template>
<v-alert v-slot:no-results :value="true" color="error" icon="warning">
Ihre Suche für "{{ search }}" brachte keine Ergebnisse.
</v-alert>
</v-data-table>
</v-card>
</template>
<script>
export default {
data() {
return {
search: '',
headers: [
{
text: 'Datum',
align: 'left',
sortable: false,
value: 'date'
},
{ text: 'Flugart', value: 'type' },
{ text: 'Beschreibung', value: 'remark' },
{ text: 'Type', value: 'plane' },
{ text: 'Betrag', value: 'fee' }
],
transactions: [{date:"",type:"",remark:"",plane:"",fee:""}],
loading: false
};
},
methods: {
},
mounted() {
}
}
</script>
app.js (relevant parts):
require('./bootstrap');
window.Vue = require('vue');
Vue.component('dashboard-component', require('./components/DashboardComponent.vue').default);
import Vuetify from 'vuetify'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Vue.use(Vuetify)
import 'vuetify/dist/vuetify.min.css'
import DutyComponent from './components/DutyComponent.vue';
import TransactionComponent from './components/TransactionComponent.vue';
import AdminComponent from './components/AdminComponent.vue';
const moment = require('moment')
require('moment/locale/de')
Vue.use(require('vue-moment'), {
moment
})
let router = new VueRouter({
routes: [
{
path: '/duties',
name: 'Dienste',
component: DutyComponent,
},
{
path: '/transactions',
name: 'Pilotenkonto',
component: TransactionComponent,
},
{
path: '/admin',
name: 'Admin',
component: AdminComponent,
}
]
})
const app = new Vue({
el: '#app',
router,
data() {
return {
logged_in: true
};
}
});
app.blade.html (relevant parts):
<div id="app">
<v-app id="inspire" dark>
<dashboard-component ref="dashboard" v-if="logged_in"></dashboard-component>
</v-app>
</div>
DashboardComponent.vue (relevant parts - here also the router view is located):
<main>
<v-content>
<v-container>
<v-fade-transition mode="out-in">
<router-view></router-view>
</v-fade-transition>
</v-container>
</v-content>
</main>
I have tried everything without any luck, respecting the documentation.
I can't see any problem. I can only guess that the problem may be dedicated to the difference of embedding the component into the app with the VueRouter instead.
Please help ! Thank you !

You are running Vue 2.5.17 and the v-slot directive was introduced in Vue 2.6.0 as you can read in the official documentation. Try updating your dependencies and see if it works.

Related

Pass component as prop in Vue JS

Intro: I am exploring Vue Js and got stuck while trying to make a dynamic data table component the problem I am facing is that I cannot pass a component via props and render it inside a table.
Problem: So basically what I am trying to do is to pass some custom component from headers prop in v-data-table such as:
headers = [
{ text: 'Name', value: 'name' },
{
text: 'Phone Number',
value: 'phone_number',
render: () => (
<div>
<p>Custom Render</p>
</div>
)
},
{ text: 'Actions', value: 'actions' }
]
So from the code above we can see that I want to render that paragraph from the render function inside Phone Number header, I did this thing in React Js before, but I cannot find a way to do it in Vue Js if someone can point me in the right direction would be fantastic. Thank you in advance.
You have 2 options - slots and dynamic components.
Let's first explore slots:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<v-chip>{{ item.phone_number }}</v-chip>
</template>
<template slot="item.company_name" slot-scope="{item}">
<v-chip color="pink darken-4" text-color="white">{{ item.company_name }}</v-chip>
</template>
</v-data-table>
</template>
The data table provides you slots where you can customize the content. If you want to make your component more reusable and want to populate these slots from your parent component - then you need to re-expose these slots to the parent component:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="props">
<slot name="phone" :props="props" />
</template>
<template slot="item.company_name" slot-scope="props">
<slot name="company" :props="props" />
</template>
</v-data-table>
</template>
If you don't know which slots will be customized - you can re-expose all of the data-table slots:
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
hide-default-footer
:options.sync="pagination"
:expanded="expanded"
class="tbl_manage_students"
height="100%"
fixed-header
v-bind="$attrs"
#update:expanded="$emit('update:expanded', $event)"
>
<!-- https://devinduct.com/blogpost/59/vue-tricks-passing-slots-to-child-components -->
<template v-for="(index, name) in $slots" v-slot:[name]>
<slot :name="name" />
</template>
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
<slot :name="name" v-bind="data" />
</template>
<v-alert slot="no-results" color="error" icon="warning">
{{ $t("no_results", {term: search}) }}"
</v-alert>
<template #footer="data">
<!-- you can safely skip the "footer" slot override here - so it will be passed through to the parent component -->
<table-footer :info="data" #size="pagination.itemsPerPage = $event" #page="pagination.page = $event" />
</template>
</v-data-table>
</template>
<script>
import tableFooter from '#/components/ui/TableFooter'; // you can safely ignore this component in your own implementation
export default
{
name: 'TeacherTable',
components:
{
tableFooter,
},
props:
{
search:
{
type: String,
default: ''
},
items:
{
type: Array,
default: () => []
},
sort:
{
type: String,
default: ''
},
headers:
{
type: Array,
required: true
},
expanded:
{
type: Array,
default: () => []
}
},
data()
{
return {
pagination:
{
sortDesc: [false],
sortBy: [this.sort],
itemsPerPageOptions: [25, 50, 100],
itemsPerPage: 25,
page: 1,
},
};
},
watch:
{
items()
{
this.pagination.page = 1;
},
sort()
{
this.pagination.sortBy = [this.sort];
this.pagination.sortDesc = [false];
},
}
};
</script>
Dynamic components can be provided by props:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<component :is="compPhone" :phone="item.phone_number" />
</template>
<template slot="item.company_name" slot-scope="{item}">
<component :is="compCompany" :company="item.company_name" />
</template>
</v-data-table>
</template>
<script>
export default
{
name: 'MyTable',
props:
{
compPhone:
{
type: [Object, String], // keep in mind that String type allows you to specify only the HTML tag - but not its contents
default: 'span'
},
compCompany:
{
type: [Object, String],
default: 'span'
},
}
}
</script>
Slots are more powerful than dynamic components as they (slots) use the Dependency Inversion principle. You can read more in the Markus Oberlehner's blog
Okay, I don't believe this is the best way possible but it works for me and maybe it will work for someone else.
What I did was I modified the headers array like this:
headers = [
{ text: 'Name', align: 'start', sortable: false, value: 'name' },
{
text: 'Phone Number',
key: 'phone_number',
value: 'custom_render',
render: Vue.component('phone_number', {
props: ['item'],
template: '<v-chip>{{item}}</v-chip>'
})
},
{ text: 'Bookings', value: 'bookings_count' },
{
text: 'Company',
key: 'company.name',
value: 'custom_render',
render: Vue.component('company_name', {
props: ['item'],
template:
'<v-chip color="pink darken-4" text-color="white">{{item}}</v-chip>'
})
},
{ text: 'Actions', value: 'actions', sortable: false }
]
And inside v-data-table I reference the slot of custom_render and render that component there like this:
<template v-slot:[`item.custom_render`]="{ item, header }">
<component
:is="header.render"
:item="getValue(item, header.key)"
></component>
</template>
To go inside the nested object like company.name I made a function which I called getValue that accepts 2 parametes, the object and the path to that value we need which is stored in headers array as key (ex. company.name) and used loadash to return the value.
getValue function:
getValue (item: any, path: string): any {
return loadash.get(item, path)
}
Note: This is just the initial idea, which worked for me. If someone has better ideas please engage with this post. Take a look at the props that I am passing to those dynamic components, note that you can pass more variables in that way.

Why is my imported VueJS Component not displayed?

I have the following Vue view named "PasswordResetView":
<template>
<v-content>
<v-card>
<v-card-title primary-title>
Passwort ändern
</v-card-title>
<v-card-text>
<v-text-field
id="password"
label="Passwort"
name="password"
prepend-icon="mdi-lock"
type="password"
/>
<v-text-field
id="passwordRepeated"
label="Passwort wiederholen"
name="passwordRepeated"
prepend-icon="mdi-lock"
type="password"
/>
<v-text-field
id="mail"
label="E-Mail"
name="mail"
prepend-icon="mdi-lock"
type="text"
/>
</v-card-text>
</v-card>
</v-content>
</template>
<script>
import axios from "axios";
export default {
name: "passwordreset",
data() {
return {
password: "",
passwordRepeated: "",
mail: "",
errormessage: "",
};
},
methods: {
changePassword() {
let payload = {mail: this.mail, password:this.password, passwordRepeated: this.passwordRepeated};
axios({
method: "post",
url: "/api/anonymous/register/pwreset",
data: payload,
}).then(() => {
this.$props.passwortresetkey = "good"
})
},
}
};
</script>
<style scoped/>
The view is imported by another vue component "PasswordReset" as following:
<template>
<div>
<PasswordReset v-if="pwresetkey === 'good'"></PasswordReset>
<div v-else>
<v-card>
<v-card-title primary-title>
Passwort ändern
</v-card-title>
<v-card-text>
Leider ist der Link ungültig.
</v-card-text>
</v-card>
</div>
</div>
</template>
<script>
import PasswordReset from "../../../components/anon/PasswordReset";
export default {
name:"passwordreset",
components: PasswordReset
};
</script>
The corresponding router:
{
path: "/pwreset",
name: "pwreset",
meta: {
requiresDisponent: false,
requiresRecurring: false,
requiresOneTime: false,
requiresAnon: true
},
component: () => import("#/views/recurring/account/PasswordReset"),
props: true
},
However, when I start the application, only the content from "PasswordReset" is shown (the v-card), but not the input fields.
Also, in the component "PasswordReset" it says that export default is ununsed.
Why is this marked as unused and the view not imported?
From vuejs docs:
If you use kebab-case
Vue.component('my-component-name', { /* ... */ })
When defining a component with kebab-case, you must also use kebab-case when referencing its custom element, such as in .
if you use PascalCase
Vue.component('MyComponentName', { /* ... */ })
When defining a component with PascalCase, you can use either case when referencing its custom element. That means both and are acceptable. Note, however, that only kebab-case names are valid directly in the DOM (i.e. non-string templates).
Try changing your password reset component like so:
<script>
import PasswordReset from "../../../components/anon/PasswordReset";
export default {
name:"passwordreset",
components: {PasswordReset}
};
</script>
VueJS Component Registration

VueJS js how to show dynamic form

I'm new to vue.I have a vue which needs to receive and show different template models.
I've tried this (simulating dynamic injection of one input field):
<template>
<b-container v-if="show">
<b-row>
<b-col class="map-dialog" cols="12" sm="6" md="4" >
<h3>{{ title }}</h3>
<component v-bind:is="fields"></component>
<b-button v-on:click="hide">Close</b-button>
</b-col>
</b-row>
</b-container>
</template>
<script>
import Vue from 'vue'
export default {
props: {
show: Boolean,
},
data() {
return {
title: null,
fields: null,
}
},
mounted() {
this.fields = Vue.component('fields', {
template: '<b-form-input v-model="text1" type="text" placeholder="Enter your name"></b-form-input>'
})
},
}
This gives an error:
[Vue warn]: You are using the runtime-only build of Vue where the template
compiler is not available. Either pre-compile the templates into render
functions, or use the compiler-included build.
What to do?
Thanks to help from #Boussadjra Brahim, I found a solution using async components.
Here is the amended code:
<template>
<b-container v-if="show">
<b-row>
<b-col class="map-dialog" cols="12" sm="6" md="4" >
<h3>{{ title }}</h3>
<FormFields/>
<b-button v-on:click="hide">Close</b-button>
</b-col>
</b-row>
</b-container>
</template>
<script>
import Vue from 'vue/dist/vue.js'
export default {
props: {
show: Boolean,
},
data() {
return {
title: null,
fields: null,
}
},
mounted() {
Vue.component('FeatureFields', function (resolve, reject) {
resolve({
template: '<b-form-input type="text" placeholder="Enter your name"></b-form-input>'
})
});
},
}
I also needed to change import Vue from 'vue' to import Vue from 'vue/dist/vue.js so that it would compile the template.

Rendering a new Vue component after click

I am using Vue-CLI. I have a Vue component which is called viewGenres.vue. This component contains Vuetify table which presents all of the current genres from Vue store. I am trying to add a new option for each genre which is "Edit".
My objective is that for each line in the table there will be an edit button. Once the button is clicked, a new component called editGenre.vue should be rendered.
This component should contain a filled-out form with all the existing details of the specific genre.
I have several questions:
1) Once I click on the edit button, the following exception appears on browser:
ReferenceError: Vue is not defined at VueComponent.editGenre
2) In order for me to load the right properties from the DB, I need to define the "ID" prop of the editGenre component. Does anyone have any recommendation on the best method to do so?
This is viewGenres.vue: (the method editGenre is the one responsible for rendering the new component).
<template>
<div class="root" ref="container">
<h2>Genres Info</h2>
<br>
<v-data-table
:headers="headers"
:items="genres"
hide-actions
class="elevation-1">
<template slot="items" slot-scope="props">
<td class="text-xs-left">{{ props.item.id }}</td>
<td class="text-xs-left">{{ props.item.name }}</td>
<td class="text-xs-left">{{ props.item.desc }}</td>
<td class="text-xs-left">{{ props.item.artists }}</td>
<td class="text-xs-left"><v-btn flat #click="editGenre(props.item.id)">EDIT</v-btn></td>
<td class="text-xs-left"><v-btn flat #click="deleteGenre(props.item.id)">Delete</v-btn></td>
</template>
</v-data-table>
</div>
</template>
<script>
import editGenre from '#/components/administratorView/Genres/editGenre.vue'
const firebase = require('../../../firebaseConfig.js')
export default {
data: function(){
return{
headers: [
{ text: 'ID', value: 'id'},
{ text: 'Name', value: 'name'},
{ text: 'Description', value: 'desc'},
{ text: 'Artists', value: 'artists'},
{ text: 'Edit Genre'},
{ text: 'Delete From DB'}
]
}
},
computed: {
genres: function(){
return this.$store.state.genre.genres
}
},
components: {
editGenre
},
methods: {
editGenre: function(id){
var ComponentClass = Vue.extend(editGenre)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
},
deleteGenre: function(id){
console.log("Trying to delete " +id)
firebase.firestore.collection("genres").doc(id).delete().then(()=>{
this.$store.dispatch('genre/getGenresFromDB')
alert("Deleted Document Successfully")
}).catch(function(error){
alert(error)
})
}
},
mounted(){
this.$store.dispatch('genre/getGenresFromDB')
}
}
</script>
<style scoped>
</style>
This is editGenre.vue:
<template>
<v-dialog v-model="editGenre" persistent max-width="500px">
<v-card>
<v-card-title>
<h2>Edit Genre {{genre.name}}</h2>
</v-card-title>
<v-card-text>
<v-text-field
v-model="name"
label="Name"
:error-messages="nameErrors"
#touch="$v.name.$touch()"
#blur="$v.name.$touch()"
/>
<v-textarea
v-model="desc"
label="Description"
box
/>
<v-combobox
v-model="artists"
label="Artists"
:items="artistNames"
:error-messages="artistsErrors"
#touch="$v.artists.$touch()"
#blur="$v.artists.$touch()"
multiple>
</v-combobox>
<v-btn
color="primary"
#click="submit">
Submit
</v-btn>
<v-btn
color="primary"
#click="close">
Close
</v-btn>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import { required } from 'vuelidate/lib/validators'
const firebase = require('../../../firebaseConfig')
export default{
data: function(){
return{
name: '',
desc: '',
artists: []
}
},
props: {
id: String
},
mounted: function(){
let docRef = firebase.firestore.collection("genres").doc(this.id)
docRef.get().then(function(doc){
if(doc.exists){
this.name = doc.data().name
this.desc = doc.data().desc
this.artists = doc.data().artists
}
else{
console.error("Doc Doesn't Exist!")
}
}).catch(function(error){
console.error(error)
})
}
}
</script>
<style scoped>
</style>
Thank You!
Tom
You missed to import Vue in your viewGenres.vue component, so add it as follow :
....
<script>
import Vue from 'vue'
import editGenre from '#/components/administratorView/Genres/editGenre.vue'
const firebase = require('../../../firebaseConfig.js')
....
You could pass props by this way :
var ComponentClass = Vue.extend(
props:{
id:{type:String, default () { return id}}
},editGenre)
and remove this :
props: {
id: String
}
according to Evan You :
It's not recommended to use new to manually construct child components. It is imperative and hard to maintain. You probably want to make your child components data-driven, using and v-for to dynamically render child components instead of constructing them yourself.

How can I bind directives to custom components in VueJS?

So I am using vuetify with vue-cli and this is my current component code:
<template>
<div>
<v-row>
<v-col xl3 md3 xs12>
<strong>{{field}}</strong>
</v-col>
<v-col xl9 md9 xs12>
{{value}}
</v-col>
</v-row>
</div>
</template>
<script>
export default {
data() {
return {
}
},
props: ['field', 'value']
}
</script>
And I am using it in my templates like this
<template>
<two-column field="Some Field" value="Some Value"></two-column>
</template>
<script>
import TwoColumnRow from './vuetify_modifications/TwoColumnRow'
...
</script>
Now everything works perfectly but what if I want to make the grid sizes dynamic? Like for example I do with something like
<two-column field="Some Field" value="Some Value" sizes="xl3 md3 xs12"></two-column>
Is that possible? Thank you in advance.
How about this:
<foo :sizes="{ xl3: '', md3: '', xs12: '' }"></foo>
And:
<template>
<div>
<v-row>
<v-col v-bind="sizes">
<strong>{{field}}</strong>
</v-col>
</v-row>
</div>
</template>
<script>
export default {
props: {
sizes: { type: Object, default: () => {} }
// ...
}
}
</script>
One way I've been able to accomplish this is through the use of computed properties.
For simplicity of creating the example I've used colors to represent what is happening. Since it seems as through all you're really asking is how could you dynamically apply classes or value based conditions inside a component, this should work with some tweaks.
const TwoColumnRow = Vue.component('two-column', {
template: '#two-column-row-template',
data: function() {
return {}
},
props: ['field', 'value', 'colors'],
computed: {
colorList: function() {
// Split the string of colors by space and return an array of values
return this.colors.split(' ');
}
}
});
const vm = new Vue({
el: '#app-container',
data: {}
});
.red {
color: red;
}
.blue {
color: blue;
}
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app-container">
<table>
<two-column field="toast" value="cheese" colors="blue red"></two-column>
</table>
</div>
<script type="x-template" id="two-column-row-template">
<tr>
<td v-bind:class="colorList[0]">{{field}}</td>
<td v-bind:class="colorList[1]">{{value}}</td>
</tr>
</script>
This runs, so you could insert some statements {{colorList}} inside the component to see what is being rendered.