V-model property is updated only after page refresh - javascript

I have two checkboxes. Their values must be equal to watched computed properties. I can see reactive changes in my Vue extension if those properties were changed but I can see new checkbox states only after page refreshing. How can I update my component if watched computed property was changed?
Here is what I have in the template:
...
<input type="checkbox" v-model="emailSending">
<span class="ml-10 checkbox-label">Email</span>
<input type="checkbox" v-model="phoneSending">
<span class="ml-10 checkbox-label">Sms</span>
...
<script>
data() {
return {
emailSending: true,
phoneSending: true,
};
},
watch: {
playerEmailSending(value) {
this.emailSending = value;
},
playerPhoneSending(value) {
this.phoneSending = value;
},
},
computed: {
...mapGetters(['getPlayerNotifications', 'getPlayer']),
playerEmailSending() {
return this.getPlayer.data.emailSending;
},
playerPhoneSending() {
return this.getPlayer.data.phoneSending;
},
},
methods: {
...mapActions(['loadPlayerNotifications']),
save() {
this.loadPlayerNotifications({
emailSending: this.emailSending,
phoneSending: this.phoneSending,
});
},
},
</script>
UPDATE:
As this page with checkboxes is a child of another page, here is what I have in my parent page:
...
// call an action which will fetch data about the player.
// This data I will get with `getPlayer` getter in my child page.
created() {
this.loadPlayer();
},
methods: {
...mapActions(['loadPlayer']),
},
...

I managed to solve this issue by removing an action from a parent page and placing it in the component's created hook. I also assign my data properties to getter's values in this hook.
created() {
this.loadPlayer();
this.emailSending = this.playerEmailSending; // getter's value
this.phoneSending = this.playerPhoneSending; // getter's value
},

Related

VueJS - Run Code after event is successfully emitted

I'm trying to clear up the form in the child component after the event containing the entered form data has been successfully passed from the child to parent component. However, I notice that the form gets cleared before the data gets propagated via the event to the parent component, such that the event passes empty values to the parent. I tried delaying the clearForm() using a timeout, but it didn't help. Is there a way to modify the behavior such that the clearForm() happens only after the event completes and the data has been saved?
Attached is the code.
Child Component
<template>
<!-- Contains a form -- >
</template>
<script>
export default {
data() {
return {
additionalInfo:
{
id: new Date().toISOString(),
fullName: '',
preAuthorize: '',
serviceAddress: ''
},
validation: {
fullNameIsValid: true,
serviceAddressIsValid: true
},
formIsValid: true,
addServiceButtonText: '+ Add Service Notes (Optional)',
serviceNotes: [],
showServiceNotes: false,
enteredServiceNote: '', //service notes addendum
}
},
computed : {
// something
},
methods: {
setServiceNotes(){
this.showServiceNotes = !this.showServiceNotes;
},
addAnotherParty(){
this.validateForm();
if(!this.formIsValid){
return;
}
this.$emit('add-parties', this.additionalInfo); //event
console.log(this.clearForm);
},
clearForm(){
this.additionalInfo.fullName = '';
this.additionalInfo.serviceAddress = '';
this.additionalInfo.preAuthorize = false;
}
}
}
</script>
Parent Component
<template>
<div>
<base-card
ref="childComponent"
#add-parties="updateAdditionalInfoList">
<!-- Wrapper for the `Parties Being Served` component-->
<template v-slot:title>
<slot></slot>
</template>
</base-card>
</div>
</template>
<script>
export default {
data() {
return {
hasElement: false,
selectedComponent: 'base-card',
additionalInfoList : [],
clearForm: false
}
},
methods: {
updateAdditionalInfoList(additionalInfo){ //save changes passed via event
this.additionalInfoList.push(additionalInfo);
console.log('emitted');
console.log(this.additionalInfoList);
setTimeout(() => {
this.$refs.childComponent.clearForm(); //clear the form in child
}, 2000);
}
}
}
</script>
Try this
addAnotherParty(){
this.validateForm();
if(!this.formIsValid){
return;
}
let emitObj = JSON.parse(JSON.stringify(this.additionalInfo));
this.$emit('add-parties', emitObj); //event
console.log(this.clearForm);
}
If your object is not deep then you can use
let emitObj = Object.assign({}, this.additionalInfo);
instead of stringify and parse

How to share a method between two components in Vue.js?

I have an Ag-Grid that has certain action buttons and dynamic data getting filled from a MongoDB database. I have a method on my MasterData.Vue file that refreshes the Grid. Each action button inside my grid's record perform update/delete operations. When I click on those buttons I have designed a customized pop up modal component in another Modal.Vue file. I want to call that RefreshGrid() method in Modal.Vue. I tried using props to share the data but same thing doesn't work on method.
MasterData.Vue Script
<script>
import { AgGridVue } from 'ag-grid-vue';
import { mapGetters } from 'vuex';
import gridEditButtons from '#/components/GridEditButton';
import MasterModal from '#/components/MasterModal';
export default {
name: 'masterData',
data () {
return {
addBtnClick: false,
delBtnClick: false,
editVisible: false,
selected: 'Business Area',
dropdown_tables: [
'Business Area',
'Council',
'Sub Area',
'Type',
'Work Flow Stage'
],
gridOptions: {
domLayout: 'autoHeight',
enableColumnResize: true,
rowDragManaged: true,
animateRows: true,
context: {
vm: null
}
}
};
},
components: {
'ty-master-modal': MasterModal,
'ag-grid-vue': AgGridVue,
gridEditButtons
},
methods: {
// Filter Grid Contents based on Dropdown selection
RefreshGrid: function () {
let cName;
if (this.selected === 'Business Area') {
cName = 'businessarea';
} else if (this.selected === 'Council') {
cName = 'council';
} else if (this.selected === 'Type') {
cName = 'typemaster';
} else if (this.selected === 'Work Flow Stage') {
cName = 'workflowstage';
}
let obj = {
vm: this,
collectionName: cName,
action: 'masterData/setMasterData',
mutation: 'setMasterData'
};
this.$store.dispatch(obj.action, obj);
}
};
</script>
Modal.Vue Script
<script>
import {mapGetters} from 'vuex';
export default {
name: 'MasterModal',
props: {
readOnly: Boolean,
entryData: Object,
addBtnClick: Boolean,
delBtnClick: Boolean,
editVisible: Boolean,
selectedTable: String
},
data () {
return {
fieldAlert: false,
isReadOnly: false,
dialog: false,
dialogDelete: false,
valid: false,
visible: false,
disable: false
};
},
computed: {
...mapGetters('masterData', {
entryState: 'entryState',
// entryData: 'entryData',
columns: 'columns',
selectedRowId: 'selectedRowId'
})
},
watch: {
addBtnClick: function (newValue, oldValue) {
this.setDialog(!this.dialog);
},
editVisible: function (newValue, oldValue) {
this.setVisible(!this.visible);
},
delBtnClick: function (newValue, oldValue) {
this.setDialogDelete(!this.dialogDelete);
}
},
methods: {
setDialog (bValue) {
this.dialog = bValue;
},
setDialogDelete (bValue) {
this.dialogDelete = bValue;
},
}
};
</script>
there are a couple of ways to achieve this.
One is to use the emit
in the MasterModal.vue component run this.$emit('refreshGrid') in the parent MasterData.Vue component use <ty-master-modal #refreshGrid="RefreshGrid" ...>
if you have a direct parent-child relationship, this is likely the best option
Another way is just to pass a function as a prop to the child component.
<ty-master-modal :onRefreshGrid="RefreshGrid" ...>
and add a prop onRefreshGrid to MasterModal.vue, then you can invoke the function.
Another way, using vuex, is to add a watch to MasterData.Vue and watch a variable in the vuex store ie. actionInvoker. when actionInvoker changes, the action executes. To change the value, set it to 0 and increment or toggle between, or set to random value. The advantage is that you can call this from anywhere.
The problem with this (and the previous) solution is that you have functionality tied to a view/component that shouldn't be there. I would recommend a third solution, which is to push the functionality into a vuex action, and then you can call it from anywhere. This would require though that you store the selected variable in vuex too, and if you want to have multiple instances of Modal and Master components, a singular store will prohibit that (unless you add support for multiple instances).

Vue Directive not updating it's value - Jquery Chosen

I am using Jquery Chosen along with Vue. This is my Vue directive:
Vue.component("chosen-select", {
props: {
value: [String, Array],
multiple: Boolean
},
template: `<select :multiple="multiple"><slot></slot></select>`,
mounted() {
$(this.$el)
.val(this.value)
.chosen({ width: '100%' })
.on("change", e => this.$emit('input', $(this.$el).val()))
},
watch: {
value(val) {
$(this.$el).val(val).trigger('chosen:updated');
}
},
destroyed() {
$(this.$el).chosen('destroy');
}
});
And using it like this:
<chosen-select v-model="basicDetailsModel.stateID" v-validate="'required'" data-vv-as="state" :state="errors.has('stateID') ? 'invalid' : 'valid'" name="stateID">
<option :value="null">Please select an option</option>
<option v-for="(state, index) in states" :key="index" :value="state.sid">{{state.nm}}</option>
</chosen-select>
If the states are assigned static value it works fine as per expectation but if I fetch the states value dynamically the chosen is not updated with latest values. It stays with the initial values.
How would I fix this issue?
Edit: This one works. Do you think this is the right way?
Vue.component("chosen-select", {
data() {
return { observer: null }
},
props: {
value: [String, Array],
multiple: Boolean
},
template: `<select :multiple="multiple"><slot></slot></select>`,
mounted() {
// Create the observer (and what to do on changes...)
this.observer = new MutationObserver(function (mutations) {
$(this.$el).trigger("chosen:updated");
}.bind(this));
// Setup the observer
this.observer.observe(
$(this.$el)[0],
{ childList: true }
);
$(this.$el)
.val(this.value)
.chosen({ width: '100%' })
.on("change", e => this.$emit('input', $(this.$el).val()))
},
watch: {
value(val) {
$(this.$el).val(val);
}
},
destroyed() {
$(this.$el).chosen('destroy');
}
});
The easiest way to fix this issue is simply not to render the select until you have options to render using v-if.
<chosen-select v-if="states && states.length > 0" v-model="basicDetailsModel.stateID" v-validate="'required'" data-vv-as="state" :state="errors.has('stateID') ? 'invalid' : 'valid'" name="stateID">
You could also play around with emitting the chosen:updated event when the component is updated.
updated(){
$(this.$el).trigger("chosen:updated")
},
which works for multiple selects, but mysteriously not for single selects.
I am not sure how you are fetching the states dynamically, but if you're using jQuery to get them, then I think that is your problem. Vue doesn't get notified if non-Vue things (like jQuery) change anything.
Even if that's not the case, this is worth reading to see why jQuery and Vue don't get along.
Can you add how you are fetching them dynamically?
Also, consider using a Vue framework like Vuetify which has a pretty good select control and is totally in Vue.

Is there any way to have multiple Vues have a computed listener working on the same value?

Setup:
I have multiple Vue components, and each component has multiple instances in different dialogs in my web app.
For each type of component I have a global state (handrailOptions in the example below) so that each type of component stays in sync across the dialogs.
I'd like for it so that when a component proceeds beyond step 1, I hide the other components in that dialog.
I have achieved this nicely using the computed / watch combo.
However, my problem is that it seems if I try to listen in with computed through more than 1 Vue instance, it hijacks the other listeners.
Problem
Below is a simplified version of what I'm working with, when the app starts up, the console logs 'computed 1' & 'computed 2'. But then when I change handrailOptions.step, only the second fires. ('computed 2' & 'watched 2')
Is there any way to have multiple Vues have a computed listener working on the same value?
handrailOptions = {
step: 1
};
Vue.component( 'handrail-options', {
template: '#module-handrail-options',
data: function() {
return handrailOptions;
},
});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 2');
}
}
});
This works as expected. I made handrailOptions responsive by making the data object of a new Vue. Making it the data object of a component, as you did, could also work, but the component would have to be instantiated at least once. It makes more sense to have a single object for your global, anyway.
handrailOptions = {
step: 1
};
// Make it responsive
new Vue({data: handrailOptions});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 2');
}
}
});
setInterval(() => ++handrailOptions.step, 1500);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="dialog-estimate-questions">
Main step {{newHandrailStep}}
</div>
<div id="dialog-checkout">
CD step {{newHandrailStep}}
</div>

Vue, checkbox and computed properies

I'm building a Vue 2 application and, in a page, I need to keep track of the value of a single checkbox. So I did this:
<template>
<div>
<input
type="checkbox"
v-model="checkboxValue"
/> Check to accept payment terms and conditions
</div>
</template>
<script>
export default {
props: {
cardData: {
type: Object,
required: true,
},
eventBus: {
type: Object,
required: true,
},
url: {
type: String,
required: true,
},
},
data() {
return {
checkboxValue: false,
};
},
computed: {
forwardCheckboxValue() {
console.log(this.checkboxValue);
this.eventBus.$emit("checkbox_value", {
checkboxValue: this.checkboxValue,
});
},
},
};
</script>
<style>
</style>
Basically I want to keep track if the checkbox is selected or not, and everytime the value changes I want to emit an event that warns me about that.
The problem is that the console.log in the computed property is not triggered.
What am I missing?
You can use computed setter and remove checkboxValue from the data option. Here is the fiddle
computed:{
checkboxValue:{
get(){
return false;
},
set(newValue){
this.$emit('checkbox-changed', newValue);
}
}
}
Or as frank provost suggested set up a watcher which should have the same name of the data property you are watching. Here is the fiddle
watch:{
checkboxValue(newValue){
this.$emit('checkbox-changed', newValue);
}
}

Categories