How to validate form in Vue? - javascript

I want to build register form in Vue.
I think I did everything okay by the book. But my question is how to trigger the validation if I using v-form from Vuetify and vue-property-decorator.
Because all the examples they have this.$refs.form.validate()... For this form it's not working.
So, how to trigger the validation when I submit the form?
This is my code:
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md6>
<v-container>
<v-card>
<v-toolbar dark color="primary">
<v-toolbar-title>Register</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form v-model="loginValid">
<v-text-field v-model="form.name.value" prepend-icon="person" name="Name" label="Name" required></v-text-field>
<v-text-field v-model="form.email.value" :rules="form.email.rule" label="Email" required type="email" prepend-icon="person"></v-text-field>
<v-text-field prepend-icon="lock" v-model="form.password.value" :rules="form.password.rule" label="Password" type="password" required></v-text-field>
</v-form>
</v-card-text>
<v-card-actions class="pa-3">
<v-spacer></v-spacer>
<v-btn ref="btn-entrar" id="btn-entrar" color="primary" #click="submit">Register</v-btn>
<router-link to="/login" class="btn btn-link">Login</router-link>
</v-card-actions>
</v-card>
</v-container>
</v-flex>
</v-layout>
</v-container>
</template>
<script lang="ts">
import { Component, Watch, Prop } from 'vue-property-decorator';
import BaseComponent from '#/modules/common/components/baseComponent.vue';
import { State, Action, Getter } from 'vuex-class';
#Component({})
export default class RegisterPage extends BaseComponent {
public loginValid: boolean = false;
public form = {
name: { value: '' },
email: {
value: '',
rule: [
(v: string) => !!v || 'Email is required',
(v: string) => /.+#.+/.test(v) || 'E-mail must be valid'
]
},
password: {
value: '',
rule: [
(v: string) => !!v || 'Password is required',
(v: string) => v.length >= 8 || ''
]
}
};
public name: string = '';
public email: string = '';
public password: string = '';
constructor() {
super();
}
public submit() {
// i don't know if this form is valid or not :(
console.log('in submit');
}
}
</script>

I have not used Typescript, but ultimately, you'll have to validate each model onSubmit. So, in the submit() function that you have, do preventDefault(), validate your fields and if all okay then go ahead actually do submit the form to the backend.
Do read the guide for general workflow: https://v2.vuejs.org/v2/cookbook/form-validation.html
Also, do check out Vuelidate and VeeValidate which are simple validation frameworks for VueJS.
p.s.: see: using Vuelidate with Typescript issue first for good pointers.

Related

Vue.js & vuex handling SMS by server-side-events

I have a app, which is basically a Call centrum. You can receive calls, call to someone, receive sms and send sms etc.. I have problem with showing my SMS on screen, when I receive event from backend, I am showing that data on screen using vuex and V-for for specific component. Problem is that when I receive another event from backend with different number, I would like to show it under that first sms, but it will overwrite first sms and show only that new sms. I was trying multiple approaches, but nothing worked for me so I hope someone will be able to show me my mistake.
Here is photo of screen with one sms (red box is where second sms should be with own informations like number...)..
Here is code where I receive events.
export default function setupStream(){
let evtSource = new EventSource('/hzs/events.sse');
evtSource.addEventListener('receive_sms', event => {
let sms_data = JSON.parse(event.data);
store.dispatch('receiveSMS', sms_data);
}, false)
}
Here is my vuex code
const state = {
sms: [],
};
const getters = {
getSMS: (state) => state.sms,
};
const actions = {
receiveSMS({ commit }, sms_data) {
commit('setSMS', sms_data);
},
};
const mutations = {
setSMS: (state, sms) => (state.sms = sms),
};
export default {
state,
getters,
actions,
mutations
}
And here is component.
<template>
<v-card>
<v-card-title class="primary white--text">
{{ $t("Communication") }}
</v-card-title>
<v-card d-flex flex-column height="100%" class="card-outter scroll">
<v-col>
<div v-for="sms in getSMS" :key="sms.id">
<v-card-actions>
<v-row>
<v-btn #click="openChat" icon class="mt-4"
><v-img
max-width="30px"
max-height="30px"
class="mt-2"
src="#/assets/icons/icon-sms.svg"
alt="icon-sms"
/></v-btn>
<v-col>
<span>{{sms.date_time}}</span> <br />
<h4>{{sms.sender}}</h4>
<!-- Dialog for Adding new Note -->
<v-dialog
v-model="showEditor"
max-width="400px"
persistent
scrollable
>
<template v-slot:activator="{ on, attrs }">
<v-btn
#click="showEditor = true"
depressed
small
v-bind="attrs"
v-on="on"
>{{$t("Add Note")}}</v-btn
>
</template>
<AddNoteDialog v-on:close-card="showEditor = false"
/></v-dialog>
</v-col>
<v-spacer></v-spacer>
<v-btn class="mt-5" icon #click="deleteCommunication"
><v-img
max-width="20px"
src="#/assets/icons/icon-delete.svg"
alt="icon-delete"
/></v-btn>
</v-row>
</v-card-actions>
<v-divider></v-divider>
</div>
<v-spacer></v-spacer>
<v-divider></v-divider>
<v-card-actions class="card-actions">
<v-row>
<v-text-field
class="ml-4"
color="primary white--text"
required
:label="$t('Mobile number')"
clearable
></v-text-field>
<v-dialog
v-model="showEditor1"
max-width="450px"
persistent
scrollable
>
<template v-slot:activator="{ on, attrs }">
<v-btn
#click="showEditor1 = true"
class="mt-5 mr-4"
depressed
icon
v-bind="attrs"
v-on="on"
><v-icon>mdi-plus-circle</v-icon></v-btn
>
</template>
<AddNummberDialog v-on:close-card="showEditor1 = false"
/></v-dialog>
</v-row>
</v-card-actions>
</v-col>
</v-card>
</v-card>
</template>
<script>
import AddNoteDialog from "#/components/UI/AddNoteDialog";
import AddNummberDialog from "#/components/UI/AddNummberDialog";
import { mapGetters, mapActions } from 'vuex';
export default {
name: "Communication",
data() {
return {
dialog: false,
showEditor: false,
showEditor1: false,
note: '',
chat: this.switchChat,
};
},
computed: {
...mapGetters(['getSMS']),
},
components: { AddNoteDialog, AddNummberDialog },
props: ["switchChat"],
methods: {
...mapActions(['setupEvents']),
openChat() {
this.$emit('openChat')
},
async deleteCommunication() {
alert("Deleted");
},
},
};
</script>
<style>
.scroll {
overflow-y: scroll;
}
.card-outter {
padding-bottom: 50px;
}
.card-actions {
position: absolute;
bottom: 0;
width: 100%;
}
</style>
I think that solution is creating new array, where I will store every single SMS that I receive. Problem is that I don't know how and where to do it.
You already have your vue state array called sms which is a good start. You'll need to update your Vuex to have an additional mutation called "addNewSMS" or something:
const mutations = {
setSMS: (state, sms) => (state.sms = sms),
addNewSMS: (state, newSMS) => state.sms.push(newSMS),
};
This will update your state.sms array to include more than one element, which you should be able to loop through using a v-for loop in your template.
Of course, you'll also need to update your actions like this:
const actions = {
receiveSMS({ commit }, sms_data) {
commit('addNewSMS', sms_data);
},
};
As a sidenote, I'd personally change the sms variable name to messages so its clearer to you and other coders that it contains multiple objects.

I want to display an error message when an email address exists in a Vuetiify form

assumption
I am creating a user registration form using Vuetify. I want to display an error if an email address already exists at registration, how can I implement this functionality using Vuetify's textfield?
The configuration is loading the email componentized in the Form. It uses v-model between components with setter and getter to be updated reactively.
What we want to achieve
I want to use v-text-field of Vuetify to show an error if the email address already exists.
Code
Email
UserFormTextFieldEmail
<template>
<v-row justify="center">
<v-col cols="12" md="10" sm="10">
<v-text-field
v-model="setEmail"
type="text"
label="email"
prepend-icon="mdi-email"
:rules="rules"
/>
<p class="caption mb-0" />
</v-col>
</v-row>
</template>
<script>
export default {
props: ['email'],
data () {
return {
rules: [
v => !!v || '',
v => /.+#.+\..+/.test(v) || ''
]
}
},
computed: {
setEmail: {
get () { return this.email },
set (newVal) { return this.$emit('update:email', newVal) }
}
}
}
</script>
Form
<v-card class="pa-7 ma-10 mx-auto" max-width="600">
<div class="login-logo">
<img
:src="logoImg"
width="70px"
>
</div>
<v-form
ref="form"
v-model="isValid"
>
<v-container>
<UserFormTextFieldUserName :name.sync="userInfo.name" />
<UserFormTextFieldEmail :email.sync="userInfo.email" :error.sync="errorMessage" /> // email
<UserFormTextFieldPassword :password.sync="userInfo.password" />
<v-row justify="center">
<v-col cols="12" md="10" sm="10">
<v-btn
:disabled="!isValid || loading"
:loading="loading"
block
class="white--text"
color="deep-purple lighten-1"
#click="signup"
>
・・・
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card>
</div>
</template>
<script>
import '#/assets/css/user-form.scss'
import logoImg from '~/assets/images/login_logo.png'
export default {
auth: false,
data () {
return {
isValid: false,
loading: false,
logoImg,
show: false,
userInfo: {
name: '',
email: '',
password: ''
},
errorMessage:''
}
},
methods: {
signup () {
this.$axios.post('/api/v1/auth', this.userInfo)
.then((response) => {
this.$store.commit('alertSwitchSuccess', true)
setTimeout(() => {
this.$store.commit('alertSwitchSuccess', false)
this.$router.replace(`/user/login`)
}, 2000)
})
.catch((e) => {
})
}
}
}
</script>
I wanted to do the same thing before, and here's my solution:
methods: {
checkemail() {
axios
.get("/api/v1/auth")
.then((response) => {
this.emails = response.data.map((a) =>
a.email);
});
},
},
This function return an Array of all the emails
data() {
return {
//email input v-model
email: "",
//declare the emails array
emails: [],
rules: [
// your rules
(v) =>
(v && this.emails.indexOf(this.email)<0)||
"this email is already existing",
],
I hope that helps

TextArea Avoid mutating a prop directly since the value will be overwritten

I know that a similar question has already been dealt with on stackoverflow. But I could not put together a solution from the proposed one. I am very ashamed.
The essence is this: I have a component and another one inside it.
The child component-VClipboardTextField is a ready-made configured text-area. I couldn't get the input out of there and I don't get an error when I try to enter it.
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"message"
code tabs-item.vue
<template>
<v-container fluid>
<v-row align="center">
<v-col cols="9">
<v-card flat>
<v-card-text>
<h1>Request</h1>
<v-container>
<v-textarea v-model="message"
placeholder="Placeholder"
label="Request"
auto-grow
clear-icon="mdi-close-circle-outline"
clearable
rows="10"
row-height="5"
#click:clear="clearMessage"
></v-textarea>
<v-textarea v-model="response"
placeholder="Placeholder"
label="Request2"
auto-grow
counter
rows="10"
row-height="5"
color="success"
></v-textarea>
<VClipboardTextField ></VClipboardTextField>
<VClipboardTextField isReadOnly></VClipboardTextField>
</v-container>
<v-row>
<v-btn
dark
color="primary"
elevation="12"
justify="end"
float-right
#click="sendRequest"
>
Send Request
</v-btn>
</v-row>
</v-card-text>
</v-card>
</v-col>
<v-col cols="3">
<schema-selector #changeSchema="onChangeSchema"></schema-selector>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'tabs-item',
props: ['response'],
data() {
return {
schema: String,
message: '',
}
},
methods: {
sendRequest() {
const message = {
message: this.message,
method: this.schema.state
}
this.$emit('sendRequest', message)
},
clearMessage() {
this.message = ''
},
onChangeSchema(selectS) {
console.log("get schema: ", selectS.state)
this.schema = selectS
}
},
}
</script>
and child VClipboardTextField.vue
<template>
<v-container>
<v-tooltip bottom
v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: 'VClipboardTextField',
props: {
isReadOnly: Boolean,
message : { type :String, default: "msg"}
},
data() {
return {
show: false,
// messageLocal: 'Response!',
iconIndex: 0,
}
},
methods: {
copyToBuffer() {
console.log("this: ", this)
navigator.clipboard.writeText(this.message);
this.toolTipChange()
setTimeout(() => this.toolTipChange(), 1000)
},
clearMessage() {
this.message = ''
},
toolTipChange() {
if (this.show)
this.show = false
}
}
}
</script>
I will be glad to see an example of the code that will explain how to correctly solve this problem without crutches!
Thanks.
you cannot modify props in components, if the initial value of message is needed as placeholder (meaning the input might not be empty at the begining), you can store the data in message prop to another data variable and use that as v-model to the textarea.
if there is no initial value for message, just use another data variable for textarea and emit an update for message in a watcher.
in code tabs-item.vue
<VClipboardTextField :message.sync="message"></VClipboardTextField>
and child VClipboardTextField.vue
<template>
<v-container>
<v-tooltip bottom
v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message_slug"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: 'VClipboardTextField',
props: {
isReadOnly: Boolean,
message : { type :String, default: "msg"}
},
data() {
return {
show: false,
// messageLocal: 'Response!',
iconIndex: 0,
message_slug: '',
}
},
watch: {
message_slug(x) {
this.$emit('update:message', x)
}
},
}
</script>
It will bind the value to message_slug and update the message on parent component when it's value changes.
Instead of watching for change every time, you can only emit when there are changes.
In your tabs-item.vue
<VClipboardTextField v-model="message"></VClipboardTextField>
In your VClipboardTextField.vue component, you can receive the v-model input prop as a "VALUE" prop and assign it to a local data property. That way u can manipulate the local property and emit only when in it is changed!
<template>
<v-container>
<v-tooltip bottom v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
v-on="on"
v-bind="attrs"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
#change="$emit('input', v)"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: "VClipboardTextField",
props: {
isReadOnly: { type: Boolean },
value: {
type: String,
},
},
data() {
return {
show: false,
message: this.value,
};
},
};
</script>

Add new properties to an object used in v-for

I want to add data (from two v-text-fields) in my product component to my data that I pass to the server. So that if the user adds the values '444' and '230' to the v-text-fields, the entry would be:
{
"444": 230
}
So far, I have hard coded the '444' and managed to get the value '230' passed. But how do i pass both '444' and '230' according to user inputs from a v-text-field?
product.vue
<v-content>
<v-container>
<code>formData:</code> {{ formData }}<br />
<v-btn color="primary" #click="create">Save</v-btn>
(Check console)<br />
<v-row>
<v-col>
<v-text-field v-for="(value, key) in formData" :key="key"
label="Card Type" v-model="formData[key]"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-content>
data() {
return {
dialog: false,
product: null,
error: null,
formData: {
444: null,
}
}
},
methods: {
async create() {
try {
await ProductService.create(this.formData);
} catch (error) {
this.error = error.response.data.message
}
}
}
What changes would i need to make in my component so that formData is based on user input from another v-text-field?
I remember your previous question. To add a new item to that basic formData hash, you would do something like this:
<div>
<v-text-field label="Product #" v-model="newKey"></v-text-field>
<v-text-field label="Card Type" v-model="newValue"></v-text-field>
<v-btn #click="$set(formData, newKey, newValue)">+</v-btn>
</div>
This uses $set to add new properties to formData.

Vuejs Vuetify Typescript validation

I am trying to use vuejs in combination with typescript. Currently I am trying to built a basic form with some validation but visual studio code keeps giving me errors.
I got the first errors from my validate function:
validate(): void {
if((this.$refs.form as Vue & { validate: () => boolean }).validate()) {
this.snackbar = true
}
},
Property 'snackbar' does not exist on type 'CombinedVueInstance
And in my clear function the second error:
clear(): void {
this.$refs.form.clear();
},
Property 'clear' does not exist on type 'Vue | Element | Vue[] |
Element[]'. Property 'clear' does not exist on type
'Vue'.Vetur(2339)
So far the component / FormComponent looks like this:
<template>
<v-container class="fill-height">
<v-row
align="center"
justify="center"
>
<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-text-field
v-model="email"
:rules="emailRules"
label="E-mail"
required
></v-text-field>
<v-select
v-model="select"
:items="items"
:rules="[v => !!v || 'Item is required']"
label="Item"
required
></v-select>
<v-checkbox
v-model="checkbox"
:rules="[v => !!v || 'You must agree to continue!']"
label="Do you agree?"
required
></v-checkbox>
<v-btn
:disabled="!valid"
color="success"
class="mr-4"
#click="storeUser"
>
Validate
</v-btn>
<v-btn
color="error"
class="mr-4"
#click="clear"
>
Leeren
</v-btn>
</v-form>
</v-row>
</v-container>
</template>
<script lang="ts">
import axios from 'axios';
import Vue from 'vue';
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
export default Vue.extend({
data: () => ({
valid: true,
name: '',
nameRules: [] = [
(v: any) => !!v || 'Name is required',
(v: any) => (v && v.length <= 10) || 'Name must be less than 10 characters',
],
email: '',
emailRules: [
(v: any) => !!v || 'E-mail is required',
(v: any) => /.+#.+\..+/.test(v) || 'E-mail must be valid',
],
select: null,
items: [
'Item 1',
'Item 2',
'Item 3',
'Item 4',
],
checkbox: false,
}),
methods: {
validate(): void {
if((this.$refs.form as Vue & { validate: () => boolean }).validate()) {
this.snackbar = true // Property 'snackbar' does not exist on type 'CombinedVueInstance<Vue ...
}
},
clear(): void {
this.$refs.form.clear();
//Property 'clear' does not exist on type 'Vue | Element | Vue[] | Element[]'.
//Property 'clear' does not exist on type 'Vue'.Vetur(2339)
},
storeUser(): void {
this.validate();
}
}
})
</script>
Try this.
export default {
data: () => ({
// Property 'snackbar' does not exist
snackbar: false
}),
methods:{
clear(): void {
// Property 'clear' does not exist on type
// formElement.reset()
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
this.$refs.form.reset();
}
}
}

Categories

Resources