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.
Related
I have the following component based on Vuetify card component:
CardTemplate
<template>
<HeightCalculator>
<template #default="{ height }">
<v-card>
<v-card-title ref="title">
{{ title }}
</v-card-title>
<v-card-text
class="overflow-y-auto"
:style="{ height: calcHeight(height) }"
>
<slot></slot>
</v-card-text>
</v-card>
</template>
</HeightCalculator>
</template>
<script>
import HeightCalculator from '../HeightCalculator.vue'
export default {
props: {
title: {
type: String,
required: true
}
},
components: {
HeightCalculator
},
methods: {
calcHeight (height) {
return `calc(${height} - ${this.$refs.title.clientHeight})`
}
}
}
</script>
NOTE: HeightCalculator is just a renderless component which returns a calculated height (in this case is going to be something like 50vh).
As you can see this.$refs.title is undefined therefore I cannot access to its .clientHeight. What am I doing wrong?
How do I access the data entered in my input elements, which are passed through via a slot, to my child component that opens up a modal with the form elements inside of it?
I've been reading the vue docs about scoped slots but honestly, I just can't figure it out how to make it work in my example. None of the examples make use of an input element with a v-model that is being passed to the child component.
I have created a component "BaseFormModal" which contains the following code:
Note that the validation (vee-validate) occurs inside here, so this child component emits a "submit" event when the data is considered valid, which I then pick up in my parent component.
<template v-slot:default="slotProps">
<b-modal ref="base-form-modal" :title="title" :no-close-on-backdrop="true" #ok.prevent="onSubmit">
<validation-observer ref="observer" v-slot="{handleSubmit}">
<b-form ref="form" #submit.stop.prevent="handleSubmit(onSubmit)">
<slot />
</b-form>
</validation-observer>
</b-modal>
</template>
<script>
import { ValidationObserver } from 'vee-validate'
export default {
name: 'BaseFormModal',
components: {
ValidationObserver,
},
props: {
title: {
type: String,
required: true,
},
},
data () {
return {
formData: {},
}
},
methods: {
async onSubmit () {
let valid = await this.$refs.observer.validate()
if (!valid) {
return
}
this.$emit('submit', this.formData)
this.$nextTick(() => {
this.$refs['base-form-modal'].hide()
})
this.formData = {}
},
showModal () {
this.$refs['base-form-modal'].show()
},
},
}
</script>
<style lang="scss" scoped>
</style>
In my page, I have a button which opens up the modal, like so:
<b-button variant="primary" #click="$refs['addOrgUserModal'].showModal()">
<i class="far fa-plus" aria-hidden="true" /> {{ $t('organisation_settings_manage_users_add_user') }}
</b-button>
Then I have defined the base form modal component in my page as this:
<base-form-modal
ref="addOrgUserModal"
:title="$tU('organisation_settings_manage_users_add_user_modal_title')"
#submit="addOrgUser"
>
<b-row>
<b-col md="6">
<form-control-wrapper :rules="{required: true}" :label="$tU('first_name_label')">
<b-form-input
v-model="user.firstName"
type="text"
lazy-formatter
:formatter="trimSpaces"
:placeholder="$t('first_name_field_placeholder')"
/>
</form-control-wrapper>
</b-col>
<b-col md="6">
<form-control-wrapper :rules="{required: true}" :label="$tU('family_name_label')">
<b-form-input
v-model="user.familyName"
type="text"
lazy-formatter
:formatter="trimSpaces"
:placeholder="$t('family_name_field_placeholder')"
/>
</form-control-wrapper>
</b-col>
</b-row>
</base-form-modal>
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
We've got the following parent component (App.vue)
<template>
<b-container
id="app"
fluid
class="px-0"
>
<b-row no-gutters>
<side-bar :routes="routes"/>
<b-col>
<notifications position='top center' />
<user-info :routes="routes"/>
<b-row no-gutters class="router-view">
<b-col>
<router-view/>
</b-col>
</b-row>
</b-col>
</b-row>
</b-container>
</template>
<script>
import UserInfo from './views/navbar/UserInfo'
import SideBar from './views/sidebar/SideBar'
import AuthService from '#/services/auth.service'
import NotificationsService from '#/services/notifications.service'
export default {
name: 'App',
components: { UserInfo, SideBar },
data () {
return {
routes: [
{ name: 'app.dashboard', params: {}, icon: 'home', text: 'Dashboard' },
{ name: 'app.jobs', params: {}, icon: 'briefcase', text: 'Jobs' },
{ name: 'app.firms', params: {}, icon: 'th-large', text: 'Firms' }
// { name: '#', params: {}, icon: 'user', text: 'People' },
// { name: '#', params: {}, icon: 'gavel', text: 'Bids' },
// { name: '#', params: {}, icon: 'users', text: 'MF People' },
// { name: '#', params: {}, icon: 'chart-bar', text: 'Reports' }
]
}
},
created () {
this.loginOnLoad()
},
methods: {
async loginOnLoad () {
this.authService = new AuthService()
let user = this.authService.sso()
if (!user) {
this.loginFailed = true
await this.login()
await this.authService.assignAccessTokenToHeader()
NotificationsService.successNotification('Logged in successfully')
} else {
await this.authService.assignAccessTokenToHeader()
NotificationsService.successNotification('Welcome Back')
}
}
}
}
</script>
<style>
</style>
assignAccessTokenToHeader dispatches a Vuex action which assigns the result of the authenticated user in the store
This is the child component (Dashboard.vue)
<template>
<div>
<b-row class="border-bottom" no-gutters>
<b-col sm="12" class="px-4 py-3">
<h3 class="text-uppercase">Dashboard</h3>
<h5 class="font-weight-normal text-secondary mb-0">Personalised Favourites</h5>
</b-col>
</b-row>
<b-row no-gutters>
<b-col>
<b-row no-gutters>
<b-col sm="6">
<b-card title="Firms" class=""></b-card>
</b-col>
</b-row>
<b-row no-gutters>
<b-col sm="6">
<b-card title="Firms" class=""></b-card>
</b-col>
</b-row>
<b-row no-gutters>
<b-col>
<b-card title="Firms" class=""></b-card>
</b-col>
</b-row>
<b-row no-gutters>
<b-col>
<b-card title="Firms" class=""></b-card>
</b-col>
</b-row>
<!-- <b-card title="Bids" class="d-inline-flex w-25">-->
<!-- </b-card>-->
<!-- <b-card title="Jobs" class="d-inline-flex w-50">-->
<!-- </b-card>-->
<!-- <b-card title="People" class="d-inline-flex w-50">-->
<!-- </b-card>-->
</b-col>
</b-row>
</div>
</template>
<script>
import AppService from '#/services/app.service'
import { mapState } from 'vuex'
export default {
name: 'Home',
created () {
this.loadGridAndContent(this.userDetails.psref)
},
computed: {
...mapState({
userDetails: state => state.auth.user
})
},
methods: {
async loadGridAndContent (psRef) {
let data = await AppService.loadDashboard(psRef)
console.log(data)
}
}
}
</script>
<style scoped>
</style>
Dashboard.vue is rendered inside the router-view of App.vue and the route is /
Now, we would like App.vue's created hook to always be ran first but it looks like Dashboard.vue tries to run its created hook before the App.vue one and hence fails because it uses psref which is acquired in the parent.
What would be a work around that or a correct way to implement so that user login information is always acquired first, stored in the Vuex and then child components can use it?
Normally, whenever your child component runs a mounted(), that is dependent on a parents action or mounted lifecycle hook, you can avoid it loading beforehand by putting a v-if on your child component. As soon as your parent has finished its task, you simply toggle a variable to true and then your child gets loaded, so you avoid empty or null values.
I'm using form validation in application, I want my validation error message to be translated. It is getting translated only after reset form.
I have tried translation in computed property, and it's working when I do
computed as below, but error message is not disappearing after touched:
{
return [this.$t('LANGUAGE')]
}
In App Component
<template>
<div id="app">
<grid_Component/>
<div class="locale-changer" style="background: black">
<select v-model="$i18n.locale">
<option v-for="(lang, i) in lang" :key="`Lang${i}`" :value="lang">{{
lang }}
</option>
</select>
</div>
</div>
</template>
<script>
import form_Component from './components/form_Component.vue'
import form_Component from './components/form_Component.vue'
import i18n from './i18n'
export default {
data () {
return{
lang: ['GB', 'NO']
}
},
name: 'app',
components: {
form_Component,
},
}
</script>
GB.json file
{
"LANGUAGE": "English"
}
NO.json file
{
"LANGUAGE": "Norwegian"
}
form component
<template>
<v-form ref="form" v-model="valid" lazy-validation>
<v-text-field v-model="name" :counter="10" :rules="nameRules"
label="Name" required>
</v-text-field>
<v-btn :disabled="!valid" color="success" #click="validate">
Validate
</v-btn>
<v-btn color="error" #click="reset">
Reset Form
</v-btn>
<v-btn color="warning" #click="resetValidation">
Reset Validation
</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
valid: true,
name: '',
}),
methods: {
validate () {
if (this.$refs.form.validate()) {
this.snackbar = true
}
},
reset () {
this.$refs.form.reset()
},
resetValidation () {
this.$refs.form.resetValidation()
}
},
computed: {
nameRules(){
return [v => !!v || this.$t('LANGUAGE')]
}
}
}
</script>