I'm building an application using Vuex and i'm not sure how to run a function when my data object meets a few user defined conditions.
<template>
<swiper-slide v-for="(photon,key) in this.$store.state.photons" :key='key'>
<ul>
<li><label for="tempAlert">Temp exceeds</label><input v-model="photon.user.tempAlert" type="number"></li>
<li><datetime type='datetime' v-model="photon.user.alertTime" use12-hour auto class="theme-orange">
<label for="alertTime" slot="before">Notify at</label></datetime></li>
</ul>
</swiper-slider>
<template>
<script>
methods:{
photonAlert(photon) {
if(
photon.user.alertTime>=new Date() &&
photon.user.tempAlert>=photon.data.tempF[photon.data.tmepF.length-1][1]
) {
this.sendnotification(photon)
}
},
}
</script>
What is the proper way to go about watching for changes and running a function when conditions are met?
Vue has a facility for this, watch:
watch: {
photon: function (photon) {
if(...
},
As Jeff pointed out in comments, you are handling an object and may need to use deep: true to catch changes:
watch: {
photon: {
handler: function (photon) { if(... },
deep: true
}
},
Related
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)
}
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
},
My component looks like this:
<template>
<div>
<div v-if="!loaded">
<p><i class="fas fa-spinner fa-spin"></i> Loading feed</p>
</div>
<div v-else>
<div data-slider ref="feedSlider" v-if="length > 0">
<div class="swiper-wrapper">
<div class="slide" v-for="record in records" :key="record.id">
<slot :record="record"></slot>
</div>
</div>
</div>
<div v-else>
<p>There are no records available.</p>
</div>
</div>
</div>
</template>
<script>
import Swiper from 'swiper';
import AjaxCaller from '../../mixins/AjaxCaller';
export default {
mixins: [AjaxCaller],
data() {
return {
loaded: false,
records: [],
length: 0,
}
},
mounted() {
this.makeCall(this.success, this.failure);
},
methods: {
success(response) {
this.loaded = true;
if (!response.data.records) {
return;
}
this.records = response.data.records;
this.length = this.records.length;
if (this.length < 2) {
return;
}
setTimeout(() => {
this.initiateSlider();
}, 1000);
},
initiateSlider() {
(new Swiper(this.$refs.feedSlider, {
effect: 'slide',
slideClass: 'slide',
slideActiveClass: 'slide-active',
slideVisibleClass: 'slide-visible',
slideDuplicateClass: 'slide-duplicate',
slidesPerView: 1,
spaceBetween: 0,
loop: true,
speed: 2000,
autoplay: {
delay: 5000,
},
autoplayDisableOnInteraction: false,
}));
},
failure(error) {
this.stopProcessing();
console.log(error);
}
}
}
</script>
The imported mixin AjaxCaller, which works fine with any other component:
<script>
export default {
props: {
url: {
type: String,
required: true
},
method: {
type: String,
default: 'post'
}
},
data() {
return {
processing: false
}
},
computed: {
getMethodParams() {
if (this.method === 'post') {
return {};
}
return this.requestData();
},
postMethodData() {
if (this.method === 'get') {
return {};
}
return this.requestData();
}
},
methods: {
requestData() {
return {};
},
startProcessing() {
this.processing = true;
this.startProcessingEvent();
},
stopProcessing() {
this.processing = false;
this.stopProcessingEvent();
},
startProcessingEvent() {},
stopProcessingEvent() {},
makeCall(success, failure) {
this.startProcessing();
window.axios.request({
url: this.url,
method: this.method,
params: this.getMethodParams,
data: this.postMethodData
})
.then(success)
.catch(failure);
}
}
}
</script>
And here's how I call it from within the view:
<feed-wrapper url="{{ route('front.news.feed') }}">
<div slot-scope="{ record }">
<p>
<a :href="record.uri" v-text="record.name"></a><br />
<span v-text="record.excerpt"></span>
</p>
</div>
</feed-wrapper>
Everything works fine in any browser other than IE 11 (and lower).
It even works in Edge - no issues what so ever.
In IE I get
[Vue warn]: Failed to generate render function:
Syntax Error: Expected identifier in ...
It doesn't even get to execute method call from within the mounted segment.
I use laravel-mix with Laravel so everything is compiled using webpack with babel so it's not ES6 related issue.
I've already spent whole night trying to un-puzzle this so any help would be much appreciated.
I know you've already said that you don't believe it's an ES6 issue but the evidence suggests it is.
IE11 doesn't support destructuring. If you type something like var {record} = {} into your IE11 console you'll see this same error message, 'Expected identifier'.
Try doing a search through the compiled code in your original error message and look for the word record. I suspect you'll find something like this:
fn:function({ record })
If you see that it means that the destructuring has made it to the browser without being compiled through Babel.
Exactly why this is happening depends on where you're using that scoped slot template. If you're using it inside a single-file component it should be going through Babel but if you aren't then it may be making it to the browser without transpiling. You said that you're calling it 'from within the view' but that doesn't clarify exactly how you're using it. There's a note about this in the docs, for what it's worth:
https://v2.vuejs.org/v2/guide/components-slots.html#Destructuring-slot-scope
Assuming you aren't able to fix the transpiling problem directly (e.g. by moving the template to somewhere it'll go through Babel) you can just remove the ES6 destructuring. So something like:
<div slot-scope="slotProps">
and then using slotProps.record instead of record in the code that follows.
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.
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);
}
}