Vue, checkbox and computed properies - javascript

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);
}
}

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

watch hook multiple variable in Vuejs [duplicate]

My data object:
data: {
selected: {
'type': null,
'instrument': null
},
My template:
<select v-model="selected['instrument']" #change="switchFilter('instrument', $event)">
<option v-for="instrument in instruments" :value="instrument.value">#{{ instrument.text }}</option>
</select>
<select v-model="selected['type']" #change="switchFilter('type', $event)">
<option v-for="type in types" :value="type.value">#{{ type.text }}</option>
</select>
How can I watch both selected indexes at the same time? I want to do something like this everytime any of the indexes updates:
watch: {
selected: function(o, n) {
...
}
}
You can use deep option provided by the watcher from vue. As stated in the docs:
To also detect nested value changes inside Objects, you need to pass in deep: true in the options argument. Note that you don’t need to do so to listen for Array mutations.
You code will look like following:
watch: {
'selected': {
handler: function (val, oldVal) {
console.log('watch 1', 'newval: ', val, ' oldVal:', oldVal)
},
deep: true
}
}
I think you can do this:
watch: {
$data: {
handler: function(val, oldVal) {
console.log(val)
},
deep: true
}
},
watch: {
'selected.type': function (newSelectedType) {
console.log(newSelectedType)
},
'selected.instrument': function (newSelectedinstrument) {
console.log(newSelectedinstrument)
}
}
If you are just trying to calculate a new data from selected, you can just use computed properties, since the data of Vue are reactive, the computed properties can also detect the changes of data.
If you want to use a single function to watch the entire object, you can use $watch with deep: true:
mounted () {
this.$watch('$data.selected', this.onSelectedUpdate, { deep: true })
}
note that '$data.selected' is a string, Vue will parse it.
and in your methods:
onSelectedUpdate (newSelected) {
console.log(newSelected)
}

V-model property is updated only after page refresh

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
},

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).

Using prop in server table

Hi All I'm having an issue with vue tables 2.
I pass my application a Vue component with the following:
<tenant-applications :url="{{ json_encode($tenant->websites->first()->hostnames()->first()->fqdn) }}/api/applications"></tenant-applications>
Which returns "http://anotherdomain.com/api/endpoint"
But when I do the following for Vue Tables 2 I get the following error with this config: "Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead."
<template>
<v-server-table url="//{{url}}" :columns="columns" :options="options"></v-server-table>
</template>
<script>
import {ServerTable, ClientTable, Event} from 'vue-tables-2';
Vue.use(ClientTable, {}, false, 'bootstrap4');
export default {
data: function () {
return {
applications: "",
columns: ['Application', 'Type', 'Lender', 'Options', 'Sale', 'Rate', 'Broker'],
tableData: applications,
options: {
perPage:25,
perPageValues:[25],
filterable: false,
}
}
},
props: {
url: String,
},
created (){
},
methods: {
}
}
}
</script>
Is there a better way to achieve what I need? I am using Laravel so can only access that url variable from the blade template
Just add computed to solve this and bind to url:
// template
<v-server-table :url="urlForServerTable" :columns="columns" :options="options"></v-server-table>
// component
...
computed: {
urlForServerTable() {
return `//${this.url}`
}
}
...

Categories