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 the following code:
Vue.component('abc', {
...
template: `
<checkbox label="unrelated" />
...
<div>{{renderTypeValue(type)}}</div>
`
methods: {
renderTypeValue(type, val) {
if(type === "number") return parseInt(val, 10);
if(type === "text") return val;
//some others...
if(type === "checkbox") return ???
I'd like to return a Vue component here. I use the same checkbox above, but I can't figure out the syntax to be able to return it from the Vue method.
Trying to render the v-alert if the value returns true in the method. Currently, nothing is displaying, what am I missing?
My Code:
<v-container>
<v-row style="margin:0px;">
<template v-if="isTestBusy()">
<v-alert type="info" color="#fb8c00" style="font-size: 14px;">
test initiated, waiting for test results
</v-alert>
</template>
</v-row>
</v-container>
mounted () {
this.pingTimer = setInterval(async function () {
that.pendingTests = await new Promise(function (resolve) {
resolve(utils.getPendingTests(that.accountnumber, that.testType))
})
var arrayLength = that.pendingTests.Table1.length;
for (var i = 0; i < arrayLength; i++) {
if (that.pendingTests.Table1[i].DiaType === that.pgType) {
that.isTestBusy(that.pendingTests.Table1[i].CPEId)
}
}
}, 5000)
},
methods : {
isTestBusy(cpe) {
try {
let tmp = this.pendingTests.Table1
// console.log(tmp)
let retVal = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step2ResponseCode
//console.log(retVal)
let retValRes = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step4Result
//console.log(retValRes)
if (retVal === 0) {
return true
}
if ((retVal === 200) && (retValRes === '')) {
return true
}
return false
} catch (error) {
return false
}
},
}
Just extra information the method and mounted is working. Its just the HTML part I am uncertain about of what exactly needs to be done to make it render.
v-alert has its own value attribute that does this and you won't need the template with v-if.
try this:
<v-alert type="info" color="#fb8c00" style="font-size: 14px;" :value="isTestBusy()" transition="scale-transition">
test initiated, waiting for test results
</v-alert>
also you need to call your method. it's probably best if you use watch: and call your method whenever your table changes and create a boolean variable inside your data() and put the returned value of your method inside it and have your alert's value attribute to work with it. (and Vue will react to changes to variable defined inside data).
do it like:
<v-alert type="info" color="#fb8c00" style="font-size: 14px;" :value="busy" transition="scale-transition">
test initiated, waiting for test results
</v-alert>
data() {
busy: false,
}
watch: {
pendingTests: {
deep: true,
handler: function(val) {
//put some if here to match your situation and then call your method like:
this.busy = this.isTestBusy(yourParams)
}
},
}
methods : {
isTestBusy(cpe) {
try {
let tmp = this.pendingTests.Table1
// console.log(tmp)
let retVal = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step2ResponseCode
//console.log(retVal)
let retValRes = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step4Result
//console.log(retValRes)
if (retVal === 0) {
return true
}
if ((retVal === 200) && (retValRes === '')) {
return true
}
return false
} catch (error) {
return false
}
},
}
optional: I suggest you also use the transition attribute with v-alert if you want. it just makes it look better.
Fixed it:
<v-alert type="info" color="#fb8c00" style="font-size: 14px;" v-if="isTestBusy" transition="scale-transition">
test initiated, waiting for test results
</v-alert>
data () {
return {
isTestBusy: false
}
},
mounted () {
this.pingTimer = setInterval(async function () {
that.pendingTests = await new Promise(function (resolve) {
resolve(utils.getPendingTests(that.accountnumber, that.testType))
})
var arrayLength = that.pendingTests.Table1.length;
for (var i = 0; i < arrayLength; i++) {
if (that.pendingTests.Table1[i].DiaType === that.pgType) {
that.isTestBusy(that.pendingTests.Table1[i].CPEId)
}
}
}, 5000)
},
methods : {
isTestBusy(cpe) {
try {
let tmp = this.pendingTests.Table1
// console.log(tmp)
let retVal = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step2ResponseCode
//console.log(retVal)
let retValRes = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step4Result
//console.log(retValRes)
if (retVal === 0) {
this.busyPingTest = true
return true
}
if ((retVal === 200) && (retValRes === '')) {
this.busyPingTest = faslse
return true
}
return false
} catch (error) {
return false
}
},
}
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>
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.