Changing value in watching cann`t work in some case with Vue - vue.js

I want to change number to int in watch, but it doesn`t work.
index.vue:
<template>
<test v-model="value" />
</template>
<script>
import Test from './test.vue';
export default {
components: {
test: Test,
},
data() {
return {
value: 1,
};
},
watch: {
value(val) {
this.value = parseInt(val);
// this.value = 2;
}
}
};
</script>
test.vue:
<template>
<div class="el-input-number">
<input
:value="currentValue"
#input="handleInput"
ref="input"
/>
</div>
</template>
<script>
export default {
name: 'ElInputNumber',
props: {
value: {
default: 0
},
},
data() {
return {
currentValue: 1
};
},
watch: {
value(value) {
console.log('value change', value);
let newVal = Number(value);
if (isNaN(newVal)) return;
this.currentValue = newVal;
this.$emit('input', newVal);
}
},
computed: {
},
methods: {
setCurrentValue(newVal) {
const oldVal = this.currentValue;
if (oldVal === newVal) return;
this.$emit('change', newVal, oldVal);
this.$emit('input', newVal);
this.currentValue = newVal;
},
handleInput(e) {
const value = e.target.value;
const newVal = Number(value);
if (!isNaN(newVal)) {
this.setCurrentValue(newVal);
}
}
}
};
</script>
The main code is this.value = parseInt(val).
The initial value is 1. I type 1.1 in input. Then I get changes in watching.
After I set this.value, the value watching function isn't called. There is no log.
What's more strange, if I change this.value = parseInt(val) to this.value = 2, it works once, but the second time I change the value, it doesn`t work.
Code is here: https://github.com/lext-7/vue-watch-component-demo.
Run it, and type 1.1 in that input. You will find that it won't become 1.

Related

Toggle button component how to reset from parent

There is a toggle component, which I connect to the parent. Found a bug, haven't found a solution yet.
toggle-button
<template>
<label :for='id + "_button"' :class='{"active": isActive}' class='toggle__button'>
<input type='checkbox' :id='id + "_button"' v-model='checkedValue'>
<span class='toggle__switch'></span>
</label>
</template>
<script>
export default ({
props: {
defaultState: {
type: Boolean,
default: false
},
id: {
type: String,
default: 'primary'
}
},
data() {
return {
currentState: this.defaultState
};
},
computed: {
isActive() {
return this.currentState;
},
checkedValue: {
get() {
return this.defaultState;
},
set(newValue) {
this.currentState = newValue;
this.$emit('change', newValue);
}
}
},
methods: {
reset() {
this.currentState = false ;
}
}
});
</script>
how can i succinctly make a reset button? I use this option now, but after reset, when I click on toggle, it does not work the first time.
<button
#click='
$refs.toggleOriginal.reset($event),
$refs.toggleAnalog.reset($event),
$refs.toggleAvailable.reset($event)
'
>
reset
</button>
Each toggle has ref in parent.
I think what you may be saying is that you have a toggle button that can flip a value anywhere (perhaps stored in a higher state?) and a reset button that can set the value back to it's initial default.
Keep the management of this data property handled outside of toggle, and make toggle responsible for simply flipping the value, and reset responsible for resetting it.
Here is what I would do:
// parent.vue
<template>
<div>
Toggled value: {{toggleValue}}
<resetButton v-model="toggleValue" />
<toggleButton v-model="toggleValue" />
</div>
</template>
<script>
export default {
data() {
return {
toggleValue: false,
}
}
}
</script>
// toggleButton.vue
<template>
<button #click="toggle">Toggle</button>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
}
},
computed: {
toggleValue: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
}
}
},
methods: {
toggle() {
this.toggleValue = !this.toggleValue
}
}
}
</script>
// resetButton.vue
<template>
<button #click="reset">Reset</button>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
}
},
methods: {
reset() {
this.$emit('input', false);
}
}
}
</script>

Error when attempting to disable Navigation Buttons in VueJs

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'])
}

How to monitor the data changes of the parent component in the child component

When the data of the parent component changes, how to realize real-time monitoring and respond in the child component.
You can watch props when props changes:
<div id="app">
<childcomponent :myprop="text"></childcomponent>
<button #click="text = 'New text'">Change text</button>
</div>
Watch for changes in ChildComponent using watch method!
new Vue({
el: '#app',
data: {
text: 'This is Text'
},
components: {
'childcomponent' : {
template: `<p>{{ myprop }}</p>`,
props: ['myprop'],
watch: {
myprop: function(newValue, oldValue) {
console.log('Prop changed and new value is: ', newVal, ' | old value: ', oldValue);
}
}
}
}
});
https://v2.vuejs.org/v2/api/#watch
props: ["opType", "editData"],
watch: {
editData: {
immediate: true,
handler(newVal, oldVal) {
if (this.editData.pryType === "MOBN") {
this.infoIndex = 0;
} else if (this.editData.pryType === "EMAL") {
this.infoIndex = 1;
} else if (this.editData.pryType === "SVID") {
this.infoIndex = 2;
} else if (this.editData.pryType === "BBIN") {
this.infoIndex = 3;
if (this.getListSeconed) {
this.getkList();
this.getListSeconed = false;
}
}
},
deep: true
}
},
Can try like this.

Why does Vuetify Autocomplete not selecting the data being set?

I use Vuetify autocomplete as a reusable component to display a list of jobs in key/value pairs. In creating record, the component works fine but when editing where data should be filled in, the data has the value but not showing on the component.
JobDropdownSelector.vue:
................................................................................................................................................
<template>
<v-autocomplete
v-model="job"
label="Job Designation"
item-value="id"
item-text="name"
return-object
:items="jobs"
#change="onChange"
>
</v-autocomplete>
</template>
<script>
export default {
props: {
selectedJob: {
type: Object,
default: null
}
},
data() {
return {
jobs: [],
job: null
};
},
methods: {
getDataFromApi() {
axios.get("api/jobs")
.then(response => {
this.jobs = response.data.data;
})
.catch(error => {
console.log(error);
reject();
});
},
onChange(e) {
this.job = e;
this.$emit("onChange", e);
}
},
watch: {
selectedJob: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
this.job = newValue;
}
}
},
mounted() {
this.getDataFromApi();
}
};
</script>
EditForm.vue:
................................................................................................................................................
<template>
<div>
<JobDropdownSelector
v-model="job"
:selectedJob="job"
#onChange="onChangeJob"
>
</JobDropdownSelector>
</div>
</template>
<script>
import JobDropdownSelector from "../components/JobDropdownSelector";
export default {
components: {
JobDropdownSelector
},
data() {
return {
job: null
};
},
methods: {
onChangeJob(e) {
this.job = e;
},
getInitialJob() {
axios.get("api/jobs/22").then(response => {
this.job = response.data.data;
});
}
},
mounted() {
this.getInitialJob();
}
};
</script>
Display
Console
It looks like you overriding job twice: on the onChangeJob method and via v-model on JobDropdownSelector.
You can try use input event to update modek instead of emitting additional event.
JobDropdownSelector.vue
methods: {
onChange(e) {
// You don't need set job because it's connected via v-model.
// this.job = e;
this.$emit("input", e);
}
}
This will cause that job in EditForm.vue should update automatically after dropdown change.

VueJS set custom component default value from props

Hi guys I tried to create a VueJS custom component to wrap Vue Autonumeric component.
https://github.com/autoNumeric/vue-autoNumeric
In Vue Autonumeric page it specifically mention the caveat
Caveats Please note that directly setting a :value='42' on the
component will break it (really!). Do NOT do that:
So in my custom component MoneyComponent.vue, I create a v-model
This is the full code
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
methods: {
},
watch: {
amount (value) {
this.$emit('input', value);
}
},
}
</script>
Usage example
<template>
<v-money
v-model="price"
></v-money>
</template>
<script>
export default {
data() {
return {
price: 45,
}
},
methods: {
}
}
<script>
This works on on initial value from parent. However if I change the price property to 55 for example, the amount property in MoneyComponent is not changing.
What is the problem here the amount property is not reactive on second changes? How do I fix it?
Thanks
Because you're using v-model, you need to emit input event to make data changed in the parent
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
then your component
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
}
</script>