I am using Veevalidate version 2. The documentation is either broken or not useful. I am trying to use multiple errors on the field but it only displays one. What am I missing? Here is my code:
Vue.use(VeeValidate, {
fastExit: false
});
Validator.extend("number", {
getMessage: (field) =>
"Error 1",
validate: (value) => {
if (/^[^-][0-9]+|[.][0-9]+/) {
return true;
} else {
return false;
}
},
});
Validator.extend("number2", {
getMessage: (field) =>
"The field is required",
validate: (value) => {
if (value == '') {
return false;
} else {
return true;
}
},
});
<input
class="ff"
v-validate="'number|number2|required'"
v-model="application.number"
/>
</div>
<span v-show="errors.has('number')" class="error">
{{
errors.first("number")
}}
</span>
<span v-show="errors.has('number2')" class="error">
{{
errors.first("number2")
}}
</span>
TLDR: codesandbox example
According to the docs you can display multiple errors with errors.collect
<ul>
<li v-for="error in errors.collect('fieldName')">{{ error }}</li>
</ul>
In your example - the validation for number1 is incorrect, you'd need something like if (value.match(<regex>))
main.js
import Vue from "vue";
import VeeValidate, { Validator } from "vee-validate";
import App from "./App.vue";
Vue.use(VeeValidate, {
fastExit: false
});
Validator.extend("shouldBeNumber", {
getMessage: field => "Should be a number",
validate: value => {
if (value.match(/^[a-z]+/)) {
return false;
} else if (value.match(/^[0-9]+/)) {
return true;
}
}
});
Validator.extend("shouldBeLongerThan3", {
getMessage: field => "Field should be longer than 3 numbers",
validate: value => {
if (value.length < 3) {
return false;
} else {
return true;
}
}
});
Validator.extend("shouldNotIncludeSymbols", {
getMessage: field => "Should not include symbols",
validate: value => {
if (value.match(/[^\w\s]+/)) {
return false;
} else {
return true;
}
}
});
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
App.vue
<template>
<div>
<input
class="ff"
v-validate="'shouldBeNumber|shouldBeLongerThan3|shouldNotIncludeSymbols|required'"
name="number"
v-model="number"
>
<span v-bind:key="index" v-for="(error, index) in errors.collect('number')">{{ error }}</span>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
number: null
};
}
};
</script>
<style lang="scss" scoped>
.fieldset {
margin-bottom: 10px;
border: 0;
}
</style>
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 emited value from the child and returning the value in a parent, but when I'm trying to access it prints me just blank array?
I haven't dealt with promises in the past, so I don't know much about that...I know something about async and await method..but I don't know how to bundle this function into this.
Thanks in advance for your help.
This is what I have...
insertMessage.vue
export default {
data(){
return{
message:'',
isLoading:false,
}
},
methods:{
addMessage(){
if(this.message !=''){
this.sendData();
} else{
}
},
sendData(){
this.isLoading = true;
this.$http.post('/add-message' , {message:this.message, id_rooms:this.$route.params.id_rooms}).then((response) => {
console.log(response.json());
if(response.body != 'Error'){
this.message = '';
this.$root.$emit('new_message', response.json());
} else{
this.isLoading = false;
}
}, (response) =>{
this.isLoading = false;
});
}
}
}
</script>
print.vue
<template>
<div class="msg_history">
<get_message :messages="messages"></get_message>
<add_message></add_message>
</div>
</template>
<script>
import addMessage from './add-message';
import getMessage from './get-messages';
export default {
components: {
get_message: getMessage,
add_message: addMessage,
},
data() {
return {
messages: []
}
},
mounted(){
this.$root.$on('new_message', (data) => {
this.messages.push(data);
});
},
}
</script>
fetchMessages.vue
<template>
<div class="incoming_msg">
<div class="received_msg">
<div v-for="message in messages" class="received_withd_msg">
{{message.created}} //This is what I want
{{message}} //This is working but print just { "promise": {} }
</div>
</div>
</div>
</template>
<script>
export default {
props:['messages'],
data(){
return{
}
}
}
</script>
You can just do emit in then:
this.$http.post('/add-message' , {message:this.message, id_rooms:this.$route.params.id_rooms}).then((response) => {
if(response.body != 'Error'){
response.json().then( json => {
this.message = '';
console.log(json);
this.$root.$emit('new_message', json);
});
} else {
this.isLoading = false;
}
}, (response) => {
this.isLoading = false;
});
<template>
<div class="comment">
<div v-for="(comment, index) in comments" :key="index">
{{ getUser(comment.student_idx) }}
</div>
</div>
</template>
<script>
import axios from 'axios'
import server from '#/models/server'
export default {
data() {
return {
url: server,
comments: {}
}
},
props: ['idx'],
created() {
axios.get(`${this.url}/bamboo/reply?post=${this.idx}`)
.then(response => {
if (response.data.status === 200) {
this.comments = response.data.data.replies
}
})
},
methods: {
getUser (idx) {
axios.get(`${this.url}/member/student/${idx}`)
.then(response => {
if (response.data.status === 200) {
return response.data.data.member.name
}
})
}
}
}
</script>
I would like to load the comments at created and print them out using v-for.
In v-for, I would like to load the member.name from each comment
But {{ getUser(comment.student_idx) }} is undefined.
I don't know how to return data from axios
Help me please!!
Your method should not be async for stable run code. My recomendation is next code:
<template>
<div class="comment">
<div v-for="(comment, index) in comments" :key="index">
{{ comments['user'] }}
</div>
</div>
</template>
<script>
import axios from 'axios'
import server from '#/models/server'
export default {
data() {
return {
url: server,
comments: []
}
},
props: ['idx'],
created() {
axios.get(`${this.url}/bamboo/reply?post=${this.idx}`)
.then(response => {
if (response.data.status === 200) {
this.comments = response.data.data.replies;
if(this.comments)
for(let comment of this.comments){
this.getUser(comment, comment.student_idx);
}
}
})
},
methods: {
getUser (comment, idx) {
axios.get(`${this.url}/member/student/${idx}`)
.then(response => {
if (response.data.status === 200) {
this.$set(comment, 'user', response.data.data.member.name);
}
})
}
}
}
</script>
I'm new to Vue and i'm trying now to do a query update on q-autocomplete component (i have to use Quasar Framework).
This is the FormAdmin component:
<template>
<q-form #submit="submit" #reset="onReset" class="q-gutter-md">
<q-select
filled
v-model="form.UserId"
label="User Email"
use-input
hide-selected
fill-input
input-debounce="0"
:options="options"
#filter="filterFn"
hint="Mininum 2 characters to trigger autocomplete"
style="width: 250px; padding-bottom: 32px"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
<div>
<q-btn label="Submit" type="submit" color="primary"/>
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</template>
This is the basic code for the update function into the filterFn:
<script>
export default {
data () {
return {
model: null,
options: null,
}
},
props: ['form', 'submit'],
methods: {
filterFn (val, update, abort) {
if (val.length < 3) {
abort()
return
}
update(() => {
console.log(val);
})
},
onReset(){
},
}
}
</script>
I tried this code:
<script>
import gql from 'graphql-tag';
const stringOptions = gql`
query {
filterUsersListFirst {
UserEmail
}
}`
export default {
data () {
return {
model: null,
options: Object.values(stringOptions),
}
},
props: ['form', 'submit'],
methods: {
filterFn (val, update, abort) {
if (val.length < 3) {
abort()
return
}
this.$apollo.query({
query: gql`query($email: String!) {
filterUsersListByEmail(
email: $email
) {
UserEmail
}
}`,
variables: {
email: val,
}
}).then(data => {
console.log(JSON.stringyfy(data));
this.options = Object.values(data);
}).catch(error =>{
console.log({error});
});
},
onReset(){
},
}
}
</script>
I tried graphql query server-side and it works.
Also i tried to print data that the Promise returns and it results in:
{"data":{"filterUsersListByEmail":[{"UserEmail":"email1","__typename":"User"},{...}]},"loading":false,"networkStatus":7,"stale":false}
But i don't know how to append the query result in the q-select options.
I found myself the solution and i'll write it:
methods: {
filterFn (val, update, abort) {
if (val.length < 3) {
abort()
return
}
setTimeout(() => {
update(() => {
this.$apollo.query({
query: gql`query($email: String!) {
filterUsersListByEmail(
email: $email
) {
UserId
UserEmail
}
}`,
variables: {
email: val,
}
}).then(data => {
var emailList = [];
for(var i = 0; i < data.data.filterUsersListByEmail.length; i++)
{
emailList[i] = JSON.parse('{"label":"' + data.data.filterUsersListByEmail[i].UserEmail + '", "value":"' + data.data.filterUsersListByEmail[i].UserId + '"}');
}
this.options = usersList;
}).catch(error =>{
console.log({error});
});
})
}, 1000)
},
onReset(){
...
},
}
Now I am using the following code. When I use #change it is not working?
<div class="col-md-3">
<div class="form-group">
<label>Longitude</label>
<input id="lngVillage"
type="text"
class="form-control"
placeholder="Longitude of Village"
name="lngVillage"
required=""
v-model="lng"
pattern="^[0-9]+\.[0-9][0-9][0-9][0-9]$"
maxlength="7"
oninvalid="this.setCustomValidity('Enter a valid Longitude')"
oninput="this.setCustomValidity('')">
</div>
</div>
<div v-if="isDisabled == false">
</div>
My computed function is
computed: {
isDisabled() {
if (this.lng.length > 5) {
axios.get('url')
.then(response => {
})
.catch(e => {
this.errors.push(e)
})
} else {
return true;
}
}
},
After typing in the input field I need to call isDisabled(). Please help me to have a solution.
There are more ways to approach this. You can solve it using the computed setter but I believe that watchers would be more appropriate here. Set a watch on the lng data using the following code.
computed: {
isDisabled() {
return (this.lng.length > 5) ? true : false
}
},
watch: {
lng: function(newValue, oldValue) {
if (newValue.length > 5 && this.lar.length > 5) {
this.executeCall();
}
},
lar: function(newValue, oldValue) {
if (newValue.length > 5 && this.lng.length > 5) {
this.executeCall();
}
}
},
methods: {
executeCall() {
axios
.get("url")
.then(response => {})
.catch(e => {
this.errors.push(e);
});
}
}
For more details please refer to https://v2.vuejs.org/v2/guide/computed.html#Watchers.