If the submit button is clicked in a form, it should automatically scroll to the first validation error if an error exists.
I've read that I can use "scrolltoview" for this, but I don't know exactly how.
I have already tried it with a simple ScrollTo (0.0) to simply scroll up in the event of errors and it works perfectly.
However, this is not the solution I would like to have.
< script >
...
let name = 'm-form-user';
export default {
name: name,
mixins: [baseForm],
props: {
name: {
type: String,
default: name
},
title: {
type: String,
default: ''
},
type: {
type: String,
default: 'create',
validator: function(value) {
return ['edit', 'create'].indexOf(value) !== -1
}
},
},
data: () => ({
form: {
firstName: '',
lastName: '',
position: '',
email: '',
mobile: '',
roles: []
}
}),
async created() {
if (!this.isCreationForm && this.$route.params.id) {
if (!this.editingUser.length) {
await this.requestUser({
id: this.$route.params.id
});
}
Object.assign(this.form, this.editingUser);
this.form.roles.pop()
}
},
computed: {
...mapGetters({
getUser: "users/read"
}),
text() {
return {
cancel: this.$t('modules.forms.m-form-user.buttons.cancel'),
submit: this.$t('modules.forms.m-form-user.buttons.submit')
}
},
editingUser() {
return this.getUser(this.$route.params.id)
},
isCreationForm() {
return this.type === 'create'
}
},
methods: {
...mapActions({
requestCreateUser: 'users/create',
requestUpdateUser: 'users/update',
requestUser: 'users/read'
}),
async submit() {
const validAll = await this.$validator.validateAll();
const validIdentify = this.validateIdentify();
if (!validAll || !validIdentify) {
// ScrolltoView
return;
}
try {
this.setOrganizationRelation();
let user = this.isCreationForm ? await this.createUser() : await this.updateUser();
this.notify.success(this.$t(`notifications.account.userManagement.${ this.isCreationForm ? 'created':'edited'}`, {
firstName: user.firstName,
lastName: user.lastName
}))
this.redirect(this.nav.dashboard.account.users.view.name, {
id: user._id
})
} catch (e) {
if (e.response && e.response.status === 400) {
e.response.data.violations.forEach(violation => {
if (violation.propertyPath === 'username') return; //TODO temporary workaround, remove it when we get correct response from server
this.$validator.errors.add({
id: violation.propertyPath,
field: violation.propertyPath,
msg: violation.message
});
const field = this.$validator.fields.find({
name: violation.propertyPath
});
if (!field) {
throw `Field "${violation.propertyPath}" in "${this.$options._componentTag}" component don't have validation on client side!`;
}
field.setFlags({
invalid: true,
valid: false,
validated: true
});
});
} else {
this.notify.processUnhandledError(e);
}
}
},
async createUser() {
return await this.requestCreateUser({ ...this.form,
password: passwordGenerator.generate()
});
},
async updateUser() {
return await this.requestUpdateUser(this.form)
},
cancel() {
this.goBack();
},
validateIdentify() {
if (!this.form.email && !this.form.mobile) {
const fields = (({
email,
mobile
}) => ({
email,
mobile
}))(this.$refs);
Object.keys(fields).forEach((key) => {
let field = this.$validator.fields.find({
name: fields[key].name
});
this.$validator.errors.add({
id: field.id,
field: field.name,
msg: this.$t('modules.forms.m-form-user.sections.contacts.emptyContacts')
});
field.setFlags({
invalid: true,
valid: false,
validated: true
});
this.$refs.emailBlock.open();
this.$refs.mobileBlock.open();
});
return false;
}
return true;
},
setOrganizationRelation() {
const rel = {
organization: this.$user.relationships.organization
};
setRelations(this.form, rel)
}
}
} <
/script>
<m-block-form-fields :required="false">
<template #title>
{{$t('modules.forms.m-form-user.sections.personal.title')}}
</template>
<template>
<v-layout wrap>
<v-flex xs12>
<e-input-user-name v-model="form.firstName" rules="required" required-style/>
</v-flex>
<v-flex xs12>
<e-input-user-surname v-model="form.lastName" rules="required" required-style/>
</v-flex>
<v-flex xs12>
<e-input-user-position-function v-model="form.position"/>
</v-flex>
</v-layout>
</template>
</m-block-form-fields>
Try using document.querySelector to locate the first error message like below.
if (!validAll || !validIdentify) {
const el = document.querySelector(".v-messages.error--text:first-of-type");
el.scrollIntoView();
return;
}
This is based on #Eldar's answer.
Because you're changing the DOM you only want to look for the new element after the DOM has been updated.
I was able to get this to work with nextTick.
if(!validAll || !validIdentify) {
// Run after the next update cycle
this.$nextTick(() => {
const el = this.$el.querySelector(".v-messages.error--text:first-of-type");
this.$vuetify.goTo(el);
return;
});
}
First, put all your fields inside "v-form" tag
Second, give it a ref="form" as in:
<v-form
ref="form"
v-model="valid"
lazy-validation
#submit.prevent="() => {}"
>
... all your fields ...
</v-form>
Finally, for the handler of your submit method, do this as a guard clause:
async submit() {
// if a field is invalid => scroll to it
if (!this.$refs.form.validate()) {
const invalidField = this.$refs.form.$children.find((e) => !e.valid)
if (invalidField)
invalidField.$el.scrollIntoView({
behavior: 'smooth',
block: 'center',
})
return
}
// here, your form is valid and you can continue
}
Related
In my ticket processing application I currently have a back and forward button contained in my TicketRunner.vue Component, I would like to change it so that these buttons only appear if I have an associated case file, for which I've used V-If:
TicketRunner.Vue
<div class="level nav-btns" v-if='!currentTicketCaseFiles.length'>
<div class="buttons has-addons level-left">
<b-button
#click.prevent="navGoPrev()"
:disabled="currentStepIndex === 0 || navWaiting"
size="is-medium"
>
</div>
export default {
name: 'TicketRunner',
mixins: [NavStepsByIndexMixin()],
components: {
StagePresenter,
CaseFilesStage,
ParticipantsStage,
AttachmentsStage,
CaseFilesRunner,
TicketContextButtons,
},
data: function() {
return {
firstComponentsInitialization: true,
loadingConfirm: false,
confirmationModalActive: false,
confirmationSucceeded: undefined
}
},
props: {
ticketId: {
type: Number,
required: true,
},
},
provide() {
return {
contextButtons: {
capture: (name, callback, title) => this.$refs['contextButtons'].captureButton(name, callback, title),
release: (name) => this.$refs['contextButtons'].releaseButton(name),
enable: (name) => this.$refs['contextButtons'].enableButton(name),
disable: (name) => this.$refs['contextButtons'].disableButton(name),
},
};
},
computed: {
...mapGetters(['currentTicket', 'ticketCaseFiles', 'allCurrentTicketAttachments', 'currentTicketCaseFileNotAssociated',
'currentRequesterType', 'currentTicketStage', 'lastCaseFile']),
caseFiles() {
return this.ticketCaseFiles(this.ticketId);
},
ticketHasAttachments() {
return this.allCurrentTicketAttachments.length > 0;
},
isTicketAssociatedWithCaseFile() {
return !this.currentTicketCaseFileNotAssociated;
},
isFirstNavInitializationInProgress() {
return !this.navReady && this.firstComponentsInitialization;
},
isShowAttachmentsStep() {
return this.ticketHasAttachments && this.currentRequesterType !== 'unknown' &&
(this.isFirstNavInitializationInProgress || this.isTicketAssociatedWithCaseFile)
},
isCurrentTicketResolved() {
return this.currentTicket.status === 'resolved';
},
islastStep() {
return this.navLastStep() && this.lastCaseFile;
}
},
watch: {
ticketId(){
this.navigator.reset();
},
navReady() {
this.moveForwardIfReady();
this.firstComponentsInitialization = false;
}
},
methods: {
...mapActions(['confirmTicket']),
moveForwardIfReady() {
if (this.navigator.currentIndex === 0 && this.firstComponentsInitialization) {
let steps = 0
const step_names = ['case_files_stage']
for(const [_idx, name] of step_names.entries()) {
const ref_name = `step[${name}]`;
if (this.$refs.hasOwnProperty(ref_name) && this.$refs[ref_name].navReady) {
steps += 1
} else {
break
}
}
this.navigator.currentIndex += steps
}
},
confirm() {
this.$buefy.dialog.confirm({
message: this.t('tickets.stages.confirmation.simplified_confirm_reply'),
onConfirm: () => this.confirmStep()
})
},
async confirmStep() {
this.loadingConfirm = true;
const promise = this.confirmTicket(this.ticketId);
return promise.then((response) => {
this.confirmationModalActive = true;
this.confirmationSucceeded = true;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).catch(() => {
this.confirmationModalActive = true;
this.confirmationSucceeded = false;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).finally(() => this.loadingConfirm = false);
},
},
};
I then receive the following Console Error:
[Vue warn]: Property or method "currentTicketCaseFiles" 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.
I know that "!currentTicketCaseFiles.length" works successfully in the Component CaseFilesStage.vue, which makes me believe I should somehow connect the two? But importing it doesn't seem right to me either. I'm not quite sure how to tackle this issue as I'm quite new at VueJS, and would be happy for any pointers. I'll attach the CaseFilesStage.vue Component below.
CaseFilesStage.vue
<template>
<div class="hero">
<div class="block">
<template v-if="!currentTicket.spamTicket">
<b-field>
<b-input
v-model="filter"
:loading="loading"
:placeholder="t('tickets.stages.case_files.search.tooltip')"
v-on:keyup.enter.native="searchCaseFiles"
type="search"
icon="search"
:class="{ 'preview-enabled': showAttachmentsPreview}"
/>
</b-field>
<template v-if="foundCaseFiles.length">
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.search.table_title') }}</h4>
<CaseFilesSearchTable
:case-files="foundCaseFilxes"
:found-by-data-points="foundCaseFilesByParticipant"
:show-header="true"
v-slot="cf">
<b-checkbox v-if="cfBelongsToCurrentTicket(cf.id)" :disabled="true" :value="true"></b-checkbox>
<b-checkbox v-else #input="onFoundCaseFile(cf.id, $event)"></b-checkbox>
</CaseFilesSearchTable>
</template>
<div v-else-if="lookupStatus === 'notFound'">
{{ t('tickets.stages.case_files.search.not_found') }}
<!-- display button here if above is activated -->
</div>
</template>
</div>
<template v-if='currentTicketCaseFiles.length'>
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.table_title') }}</h4>
<CaseFilesTable :case-files="currentTicketCaseFiles" :show-header="true" v-slot="cf">
<DeleteButton
:model-id="cf.id"
modelName="CaseFile" >
</DeleteButton>
</CaseFilesTable>
</template>
</div>
</template>
<script>
import CaseFilesTable from '../tables/CaseFilesTable';
import CaseFilesSearchTable from '../tables/CaseFilesSearchTable';
import DeleteButton from '../../../../shared/components/controls/DeleteButton';
import { mapGetters, mapActions } from 'vuex';
import { mapServerActions } from "../../../../../../_frontend_infrastructure/javascript/lib/crudvuex_new";
export default {
name: 'CaseFilesStage',
data() {
return {
lookupStatus: 'waitingInput',
filter: '',
waiting: {},
foundCaseFiles: [],
foundCaseFilesByParticipant: {}
};
},
components: {
CaseFilesTable,
CaseFilesSearchTable,
DeleteButton
},
computed: {
...mapGetters(
['currentTicketCaseFiles', 'currentTicketCaseFileNotAssociated', 'currentTicket', 'showAttachmentsPreview']
),
loading() {
return this.lookupStatus === 'waitingServer';
},
allCaseFilesMix(){
this.currentTicketCaseFiles + this.foundCaseFiles
},
foundCaseFilesEx() {
return this.foundCaseFiles.filter((x) => !this.cfBelongsToCurrentTicket(x.id))
},
checkboxValue() {
if(!this.currentTicketCaseFileNotAssociated) {
return null;
}
return true;
},
navReady() {
return this.currentTicket.spamTicket || this.currentTicketCaseFiles.length > 0 || this.checkboxValue;
},
markSpam: {
get: function() {
return this.currentTicket.spamTicket
},
set: function(val) {
return this.updateTicket([this.currentTicket.id, { spam_ticket: val }]);
},
}
},
methods: {
...mapActions(['updateTicket']),
...mapServerActions(['createCaseFile', 'deleteCaseFile']),
cfBelongsToCurrentTicket(id){
return this.currentTicketCaseFiles.map((x) => x.caseFileId).includes(id);
},
cantAssignCaseFileCheckbox(isChecked){
if(isChecked) {
this.createCaseFile({ isCfNotAssociated: true });
} else {
this.deleteCaseFile(this.currentTicketCaseFileNotAssociated);
}
},
onFoundCaseFile(id, useIt){
console.log("onFoundCaseFile: ", id, useIt);
if(useIt) {
this.createCaseFile({ caseFileId: id });
} else {
this.deleteCaseFile(this.currentTicketCaseFiles.find({ caseFileId: id }));
}
},
searchCaseFiles() {
const newData = this.filter;
if (newData.length < 3) { // TODO: some smarter condition here
this.foundCaseFiles = [];
this.lookupStatus = 'waitingInput';
return;
}
this.lookupStatus = 'waitingServer';
this.$axios.get('case_files', { params: { "case_files.filter" : newData } })
.then((response) => {
this.foundCaseFiles = response.data.caseFilesSearchResult.caseFiles;
this.foundCaseFilesByParticipant = response.data.caseFilesSearchResult.foundByPrivateInfo;
if(this.foundCaseFiles.length > 0) {
this.lookupStatus = 'success';
} else {
this.lookupStatus = 'notFound';
}
}).catch(() => this.lookupStatus = 'error');
}
},
};
</script>
</style>
Add this to your TicketRunner.vue Component script:
computed: {
...mapGetters(['currentTicketCaseFiles'])
}
I have written some code that requires some amendments to cater for multiple alerts. It should also be able to accept a timeout duration so by default it needs to be 5000 but you should be able to override this property. Please could someone help me with this as I'm not sure how to go about it from this point.
~/store/toast-messages.js
export const state = () => ({
color: '',
message: '',
type: ''
})
export const mutations = {
showToast (state, payload) {
state.color = payload.color
state.message = payload.message
state.type = payload.type
}
}
~/plugins/toasts.js
export default ({ store }, inject) => {
inject('toasts', {
showToast ({ color = '', message = '', type = '' }) {
store.commit('toast-messages/showToast', { color, message, type })
}
})
}
nuxt.config.js
export default {
...
plugins: [
'~/plugins/toasts.js'
],
...
}
~components/toasts/Toasts.vue
<template>
<v-alert
v-model="show"
transition="slide-y-transition"
:color="color"
:type="type"
dense
prominent>
{{ message }}
</v-alert>
</template>
<script>
export default {
name: 'Toasts',
data: () => {
return {
show: false,
color: '',
message: '',
type: 'error',
}
},
watch: {
show (val) {
if (val) {
this.hideToast()
}
},
},
created () {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'toast-messages/showToast') {
this.color = state['toast-messages'].color
this.message = state['toast-messages'].message
this.type = state['toast-messages'].type
this.show = true
}
})
},
methods: {
hideToast () {
window.setInterval(() => {
this.show = false
}, 3000)
},
},
}
</script>
Called like this from anywhere in the app
this.$toasts.showToast({
color: 'red',
type: 'error',
message: err.message,
})
You can use the excellent component Vue-SNotify - example here.
I'm using v-autocomplete from vuetify.js to retrieve a list of values from API Server.
It works fine and my list of values is not empty.
But my problem is when I select the correct value from this list. My script sends another request to server to retrieve another autocomplete list.
Do you have any idea to avoid to send request when a result is selected by the user ? Or to send request only when a key is down ?
My component :
<template>
<div>
<v-autocomplete
v-model="selectValeur"
:loading="loading"
:search-input.sync="search"
:items="resultatsAutocomplete"
class="mb-4"
hide-no-data
hide-details
:label="recherche.label"
></v-autocomplete>
</div>
</template>
<script>
export default {
props: {
recherche: {
type: Object,
default: null,
},
},
data: () => ({
selectValeur: null,
loading: false,
search: null,
resultatsAutocomplete: [],
}),
watch: {
selectValeur(oldval, val) {
console.log(oldval)
console.log(val)
},
search(val) {
val && val !== this.selectValeur && this.fetchEntriesDebounced(val)
console.log(val)
if (!val) {
this.resultatsAutocomplete = []
}
},
},
methods: {
fetchEntriesDebounced(val) {
// cancel pending call
clearTimeout(this._timerId)
// delay new call 500ms
this._timerId = setTimeout(() => {
this.querySelections(val)
}, 500)
},
async querySelections(v) {
if (v.length > 1) {
this.loading = true
try {
const result = await this.$axios.$get(
'myapi/myurl',
{
params: {
racine: v,
},
}
)
this.resultatsAutocomplete = result
console.log(this.resultatsAutocomplete)
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
}
} else {
this.resultatsAutocomplete = []
}
},
},
}
</script>
Thanks,
selectValeur would no longer be null if the user has selected a value, so you could update search() to return if selectValeur is truthy:
export default {
watch: {
search(val) {
if (this.selectValeur) {
// value already selected
return
}
//...
}
}
}
Or you could use vm.$watch on the search property to be able to stop the watcher when selectValeur is set:
export default {
mounted() {
this._unwatchSearch = this.$watch('search', val => {
val && val !== this.selectValeur && this.fetchEntriesDebounced(val)
if (!val) {
this.resultatsAutocomplete = []
}
})
},
watch: {
selectValeur(val) {
if (val && this._unwatchSearch) {
this._unwatchSearch()
}
}
}
}
I found a solution to my problem.
I used the #keyup event to send the axios request and I deleted the watcher on search.
So, the API request are only sent when I press a key.
<template>
<div>
<v-autocomplete
v-model="selectValeur"
:loading="loading"
:items="resultatsAutocomplete"
:search-input.sync="search"
class="mb-4"
hide-no-data
hide-details
:label="recherche.label"
#keyup="keyupSearch"
></v-autocomplete>
</div>
</template>
<script>
export default {
props: {
recherche: {
type: Object,
default: null,
},
},
data: () => ({
selectValeur: null,
loading: false,
resultatsAutocomplete: [],
search: '',
}),
methods: {
keyupSearch(val) {
val &&
val !== this.selectValeur &&
this.fetchEntriesDebounced(this.search)
if (!val) {
this.resultatsAutocomplete = []
}
},
fetchEntriesDebounced(val) {
// cancel pending call
clearTimeout(this._timerId)
// delay new call 500ms
this._timerId = setTimeout(() => {
this.querySelections(val)
}, 500)
},
async querySelections(v) {
if (v.length > 1) {
this.loading = true
try {
const result = await this.$axios.$get(
'my-api/my-url',
{
params: {
sid: this.$route.params.sid,
service: this.$route.params.service,
type: this.recherche.mode,
racine: v,
},
}
)
this.resultatsAutocomplete = result
console.log(this.resultatsAutocomplete)
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
}
} else {
this.resultatsAutocomplete = []
}
},
},
}
</script>
I have form which has a function of creating multiple fields and for each field it has dynamic drop down. When creating a new form, the dynamic drop down is working smoothly but when I try to edit the form which loads all the data, it's having a hard time loading the data for row.section_objective_id and row.section_kpa_id since it is reliable to it's v-for index.
corporate_objectives: [],
department_objectives: [],
tableItems {
rows: [],
}
<base-select
v-model="tableItem.corporate_objective_id"
:items="corporate_objectives"
item-text="name"
item-value="id"
#change="getDepartmentObjectives()"
/>
<v-btn #click="addKPA()"</v-btn>
<div v-for="(row, index) in tableItem.rows" :key="row.id">
<base-select
v-model="row.deparment_objective_id"
:items="department_objectives"
item-text="name"
item-value="id"
#change="getSectionObjectives(index)"
/>
<base-select
v-model="row.section_objective_id"
:items="row.section_objectives"
item-text="name"
item-value="id"
#change="getSectionKPA(index)"
/>
<base-select
v-model="row.section_kpa_id"
:items="row.section_kpas"
item-text="name"
item-value="id"
/>
</div>
created() {
this.getEmployeeObjectives()
}
methods: {
addKPA() {
this.tableItem.rows.unshift({
department_objective_id: '',
section_objectives: [],
section_objective_id: '',
section_kpas: [],
section_kpa_id: '',
})
},
async getEmployeeObjectives() {
try {
let res = await axios.get('/api/employee-objectives')
this.tableItem = await Object.assign({}, res.data)
this.getDepartmentObjectives()
} catch(error) {
console.log(error)
}
},
async getCorporateObjectives() {
try {
let res = await axios.get('/api/corporate-objectives')
this.corporate_objectives = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
async getDepartmentObjectives() {
try {
let res = await axios.get('/api/corporate-department-objective',
{ params: { corporate_objective_id: this.tableItem.corporate_objective_id }})
this.department_objectives = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
async getSectionObjectives(index) {
try {
let res = await axios.get('/api/department-section-objective',
{ params: { department_objective_id: this.tableItem.rows[index].department_objective_id }})
this.tableItem.rows[index].section_objectives = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
async getSectionKPA(index) {
try {
let res = await axios.get('/api/section-kpa',
{ params: { section_objective_id: this.tableItem.rows[index].section_objective_id }})
this.tableItem.rows[index].section_kpas = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
}
Text fields in Vuetify have rules props, which take an array of functions returning true or an error string. How to make them async, so that the validation could be made server-side using XHR?
Something like:
<v-text-field :rules="[v => { axios.get('/check?value=' + val).then(() => { return true }) }]">
One solution is to set the error-messages prop:
<v-text-field v-model="input" :error-messages="errors">
and use the watch option:
new Vue({
data () {
return {
input: '',
errors: []
}
},
watch: {
input (val) {
axios.get('/check?value=' + val).then(valid => {
this.errors = valid ? [] : ['async error']
})
}
}
});
I have to do a backend validation to check if the username entered already exists. I use the swagger client library with the JSON open API v3 to call the method that checks the username value.
So I solved in this way...
In my login.js file I have defined a string property that contains the error message:
import swaggerClient from "../remote-client";
const strict = true;
const state = {
hasError: false,
error: null,
usernameAlredyExists: ""
};
const getters = {
hasError: state => state.hasError,
error: state => state.error,
usernameAlredyExists: state => state.usernameAlredyExists
};
const mutations = {
checkingUsername(state) {
state.hasError = false;
state.error = null;
state.usernameAlredyExists = "";
},
usernameCheckedKO(state) {
state.usernameAlredyExists = "Username already exists";
},
usernameCheckedOK(state) {
state.usernameAlredyExists = "";
},
errorCheckingUsername(state, error) {
state.hasError = true;
state.error = error;
},
};
const actions = {
userLogin({ commit }, { username, password }) {
// not relevant code
},
checkUsername({ commit }, { username }) {
commit("checkingUsername");
swaggerClient.userSwaggerClient
.then(
function(client) {
return client.apis.UserHealthFacility.getHfUserUsernameWithUsername(
{ username: username },
{},
{}
);
},
function(reason) {
// failed to load the JSON specification
commit("errorCheckingUsername", reason);
}
)
.then(
function(callResult) {
if (callResult.body) {
commit("usernameCheckedKO");
} else {
commit("usernameCheckedOK");
}
},
function(reason) {
// failed to call the API method
commit("errorCheckingUsername", reason);
}
);
}
};
export default {
namespaced: true,
strict,
state,
getters,
mutations,
actions
};
Then in the Login.vue file I have this code:
<v-card-text>
<v-form ref="loginForm" v-model="loginValid" lazy-validation>
<v-text-field
v-model="username"
label="Username"
:rules="[rules.required]"
:error-messages="usernameAlredyExists"
v-on:change="callCheckUsername"
></v-text-field>
<v-text-field
v-model="password"
:label="Password"
:append-icon="showPassword ? 'visibility_off' : 'visibility'"
:type="showPassword ? 'text' : 'password'"
:rules="[rules.required, rules.minLength]"
counter
#click:append="showPassword = !showPassword"
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
:disabled="!loginValid"
#click="callUserLogin"
color="primary"
round
>Login</v-btn>
</v-card-actions>
<script>
export default {
data() {
return {
username: "",
password: "",
showPassword: false,
loginValid: true,
rules: {
required: value => !!value || "Questo campo รจ obbligatorio",
minLength: value =>
value.length >= 8 || "Questo campo deve contenere almeno 8 caratteri"
}
};
},
computed: {
usernameAlredyExists() {
return this.$store.getters["login/usernameAlredyExists"];
}
},
methods: {
callUserLogin() {
if (this.$refs.loginForm.validate()) {
this.$store.dispatch("login/userLogin", {
username: this.username,
password: this.password
});
}
},
callCheckUsername(value) {
if (value) {
this.$store.dispatch("login/checkUsername", {
username: this.username
});
}
}
}
};
</script>
In this way it seems to work well