How Do I update the Dom (Re-render element) when value changes? - vue.js

So I have a variable startWeek, that is being displayed and when I click a button, a Have a method that will add 7 days to the date. I want the page to show the new date but not sure how to go about doing this?
I want to get the dates to transition in and out everytime the date range is updated, but Im not sure how to do it. I have thought about using two v-if statements and transitioning back and fourth between them but im sure there is a better way. I have looked into watchers and computed properties but im not quite sure if they are the answer or how to implement them in this given situation.
Example:
<template>
<b-button #click="subtractWeek(7)></b-button>
<span id="thingIwantToUpdateInDOM">{{ startWeek + " - " + endWeek}}</span>
<b-button #click="addWeek(7)></b-button>
</template>
export default {
data(){
return{
startWeek: null,
endWeek: null,
}
},
methods: {
addWeek(days){
this.startWeek.setDate(this.startWeek.getDate() + days)
this.endWeek.setDate(this.endWeek.getDate() + days)
},
substractWeek(7){
this.startWeek.setDate(this.startWeek.getDate() - days)
this.endWeek.setDate(this.endWeek.getDate() - days)
},
getInitialDate(){
this.startWeek = new Date();
var tempEndWeek = new Date();
this.endWeek = tempEndWeek;
},
created() {
this.getInitialDate();
}
}
}
My ultimate goal is to have the date range to swipe our transition out similar to a carousel effect on every button click or value change. Any bit of advise is greatly appreciated!

Your end goal is lots of optimizations away, but the snippet below should get you started. Your date operations are fine, so a few notes about the implementation:
I don't exactly know the internals of how Vue does state change tracking in the data objects, but I'm fairly sure it involves getter and setter property accessors. When you do this.x = new Date(); this.x.setDate(this.x.getDate() + 7);, this.x tracks the date object and not its value, so the change will not be seen by Vue. You need to clone the date first, set a new date, and then reassign it to this.x (see the navigateWeeks method below).
watch is useful when you want to react to a single, specific property change, in your case, the dynamic startWeek is a perfect candidate. If the fact that something changed is more important than what exactly, use the updated hook (typical use-case: destroying & re-initializing 3rd party library widgets with new parameters).
computed is useful for keeping a property derived from another property in sync at all times, in your example the endDate is always 7 days after the startDate, so it is a perfect candidate for this. In the snippet I also used a computed value for the ISO date format that HTML date inputs expect.
Finally, you can do quite advanced stuff with setTimeout, some CSS keyframes, and toggling a .transitioning class
Vue.component('fading-date', {
template: `
<span><input :class="className" type="date" :value="htmlValue"></span>
`,
props: {
value: { type: Date },
fadeDuration: { type: Number, default: 1 }
},
data() {
return { transitioning: false, timer: null };
},
computed: {
htmlValue() {
return this.value.toISOString().split('T')[0];
},
className() {
return this.transitioning ? 'transitioning' : '';
}
},
watch: {
value() {
clearTimeout(this.timer);
this.transitioning = true;
this.timer = setTimeout(() => {
this.transitioning = false;
}, 1000 * this.fadeDuration);
}
}
});
new Vue({
el: '#app',
data: {
selectedWeek: new Date()
},
computed: {
weekAfterSelected() {
const date = this.selectedWeek;
const endDate = new Date(date);
endDate.setDate(date.getDate() + 7);
return endDate;
}
},
methods: {
navigateWeeks(numWeeks = 1) {
const newDate = new Date(this.selectedWeek);
newDate.setDate(newDate.getDate() + (7 * numWeeks));
this.selectedWeek = newDate;
}
}
});
input[type="date"] {
background: transparent;
border: none;
}
#keyframes fade{
0% {
opacity:1;
}
50% {
opacity:0;
}
100% {
opacity:1;
}
}
.transitioning {
animation: fade ease-out 1s;
animation-iteration-count: infinite;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<fading-date :value="selectedWeek" :fade-duration="1"></fading-date>
<fading-date :value="weekAfterSelected" :fade-duration="1"></fading-date>
<br>
<button type="button" #click="navigateWeeks(-1)">A week sooner</button>
<button type="button" #click="navigateWeeks(1)">A week later</button>
</div>

Related

VueJS Leaflet 'moveend' fires multiple times

Ask for help from the community. For two weeks I can not overcome the problem with repeated firing of 'mooveend' in the project. I have tried all the advice given here. Here's what I've read and researched already, but it didn't work for me.
This is one of the tips:
moveend event fired many times when page is load with Leaflet
<template>
<div id="map"></div>
</template>
<script>
export default {
name: "ObjectMapView",
props: ['coordinate'],
data: function () {
return {
map: null,
addressPoints: null,
markers: null,
}
},
mounted: function() {
this.initializedMap();
},
watch: {
coordinate: function (val) {
this.run();
}
},
methods: {
initializedMap: function () {
this.map = L.map('map').setView([52.5073390000,5.4742833000], 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
this.markers = L.markerClusterGroup();
},
run: function () {
var map = this.map;
var markers = this.markers;
var getAllObjects = this.coordinate;
var getBoundsMarkers;
//Clearing Layers When Switching a Filter
markers.clearLayers();
this.addressPoints = getAllObjects.map(function (latlng){
return [latlng.latitude, latlng.longitude, latlng.zip, latlng.object_id, latlng.archived];
});
map.addLayer(markers);
//We give to the map only those coordinates that are in the zone of visibility of the map during the first
getBoundsMarkers = getAllObjects.filter((coord) => {
if(!coord.latitude && !coord.longitude){
return false;
}
return map.getBounds().contains(L.latLng(coord.latitude, coord.longitude));
});
/*
Responds to changing the boundaries of the map visibility zone and
transmits a list of coordinates that are in the visibility zone
*/
console.log('getAllObjects_1', getAllObjects);
map.on('moveend', function() {
console.log('moveend');
console.log('getAllObjects_2', getAllObjects);
getBoundsMarkers = getAllObjects.filter((coord) => {
if(!coord.latitude && !coord.longitude){
return false;
}
return map.getBounds().contains(L.latLng(coord.latitude, coord.longitude));
});
eventHub.$emit('sendMarkers', getBoundsMarkers);
});
// In the loop, we iterate over the coordinates and give them to the map
for (var i = 0; i < this.addressPoints.length; i++) {
var a = this.addressPoints[i];
var title = '' + a[2] + ''; //bubble
var marker = L.marker(new L.LatLng(a[0], a[1]), {
title: title
});
marker.bindPopup(title);
markers.addLayer(marker);
}
eventHub.$emit('sendMarkers', getBoundsMarkers);
}
}
}
</script>
<style scoped>
#map {
width: 97%;
height: 100%;
}
</style>
I figured it out myself.
The 'zoomend' and 'dragend' option didn't work for me. I searched a lot for a suitable option and realized that the "moveend" event fires several times because this event is created every time you move the map. Therefore it is necessary to stop this event. I got out of the situation in this way. Immediately after the map was initialized, I wrote:
map.off('moveend');
and for me it worked. Now it works fine. I will be very happy if this is useful to someone.

Unexpected side effect in "isExpiryComing" computed property

I tried to make the nearExpiry attribute to become TRUE if it is within the range of 30 days. But I hit an error which is Unexpected side effect in "isExpiryComing" computed property, is there any way I can overcome this?
I'm not sure how do I use slice at the isExpiryComing computed properties. Is there any workaround for this error?
<template>
<div class="container wrapper d-flex flex-column justify-content-center align-items-center">
<h1 class="text-info">Ingredients List</h1>
<ingredients-list class="justify-content-center"
v-for="(ingredient,index) in sortedItems"
:key="index"
:index='index'
:food-name="ingredient.food"
:food-expiry="ingredient.expiryDate"
:is-expiry="isExpiryComing.nearExpiry"></ingredients-list>
</div>
</template>
<script>
export default {
data() {
return {
ingredients: [
{
food: 'CARROT',
expiryDate: '2020-12-12',
nearExpiry: false
},
{
food: 'PAPAYA',
expiryDate: '2018-1-15',
nearExpiry: false
},
{
food: 'ORANGE',
expiryDate: '2021-10-13',
nearExpiry: false
},
{
food: 'CHICKEN',
expiryDate: '2019-4-23',
nearExpiry: false
},
{
food: 'MEAT',
expiryDate: '2021-5-23',
nearExpiry: false
},
],
}
},
computed: {
sortedItems() {
return this.ingredients.slice().sort((a, b) => {
return new Date(a.expiryDate) - new Date(b.expiryDate);
});
},
isExpiryComing() {
const now = new Date().getTime()
const expiryDate = new Date(this.expiryDate).getTime()
if (now - expiryDate > (30 * 24 * 60 * 60 * 1000)) {
this.nearExpiry = false
} else {
this.nearExpiry = true
}
return this.nearExpiry
}
},
}
</script>
It's because you're chagning another variable inside a computed property, this.nearExpiry - your use case doesn't need it.
I assume you want to check the expiration date for every product inside v-for.
I would suggest removing isExpiryComing entirely and changing the sortedItems to:
return this.ingredients
.slice()
.sort((a, b) => {
return new Date(a.expiryDate) - new Date(b.expiryDate)
})
.map((ingredient) => {
const now = new Date().getTime()
const expiryDate = new Date(ingredient.expiryDate).getTime()
return { ...ingredient, nearExpiry: now - expiryDate > 30 * 24 * 60 * 60 * 1000 }
})
Might be also a good idea to move const now = new Date().getTime() higher, to data().
A couple of issues I see with this:
isExpiryComing is scoped to your current component (like ingredients and sortedItems), not to the iteration of your v-for (like ingredient and index).
Setting to this.nearExpiry = true is likely what is causing the error. Computed properties should be pure-functions, i.e. they should not affect the state (normally this wouldn't be the case, as this should be scoped to the function with the method signature you used, but this is Vue so good luck with upholding that). If you want to affect state, you should use a watched property. (and since you are just returning this.nearExpiry, why not just make nearExpiry a local variable?).
To fix the error, just make nearExpiry a local variable instead of a vue-instance variable (this.nearExpiry). But, per my first point, that's probably not what you want either. Probably move the computed property to your ingredients-list component.

Vuejs: how to implement reactively disable datepicker

im newbie here. I want control the datepicker to be disabled automatically based on the existing api. I using the vuejs-datepicker library. I've seen the documentation and managed to implement it statically, but having problems when implementing it reactively.
This is my previous view:
<datepicker
:disabled-dates="state.disabledDates">
</datepicker>
And, my previous static value of datepicker, especially for the day:
data() {
var year = (new Date()).getFullYear()
var month = (new Date()).getMonth()
var dDate = (new Date()).getDate()
var state = {
disabledDates: {
to: new Date(year, month, dDate), // Disable all dates up to specific date
// from: new Date(2020, 0, 26), // Disable all dates after specific date
days: [0,1], // Disable Saturday's and Sunday's
}
}
return {
state: state,
day: '',
}
},
For now, here my view:
<datepicker
:disabled-dates="disabledDates">
</datepicker>
Console output:
My script:
<script>
data() {
return {
day: '',
year : (new Date()).getFullYear(),
month : (new Date()).getMonth(),
dDate : (new Date()).getDate(),
}
},
computed:{
// reactive
disabledDates: {
to: new Date(year, month, dDate), // Disable all dates up to specific date, 2020,8,8
days: [day], // Disable day, 0,1
}
},
watch: {
'day': function(day){
console.log('day: '+day)
return this.day
},
},
</script>
Thank you.
I'm pretty sure your only problem is that your syntax for computed properties is wrong. They should be functions, since they need to be run. Their dependencies are automatically determined by Vue, and when those change, the function is re-run. So, try this:
data: function() {
return {
day: '',
year: (new Date()).getFullYear(),
month: (new Date()).getMonth(),
dDate: (new Date()).getDate()
};
},
computed: {
// Here. This should be a function.
disabledDates: function() {
return {
// Make sure to use 'this.' when in a component
to: new Date(this.year, this.month, this.dDate),
days: [ this.day ]
};
}
},
watch: {
day: function(day) {
console.log(`Day: ${day}`);
return value;
}
}

Vue v-for list not re-rendering after computed data update

I am implementing pagination for a huge list of cards, I display 10 cards at once and wish to show the 10 next (or 10 previous) by clicking on two buttons.
Here's how I do it:
export default {
...
data() {
return {
pois: [], // My list of elements
pageNumber: 0, // Current page number
};
},
props: {
size: {
type: Number,
required: false,
default: 10, // 10 cards per page
},
},
computed: {
pageCount() {
// Counts the number of pages total
const l = this.pois.length;
const s = this.size;
return Math.floor(l / s);
},
paginatedData() {
// Returns the right cards based on the current page
const start = this.pageNumber * this.size;
const end = start + this.size;
return this.pois.slice(start, end);
},
},
methods: {
nextPage() {
this.pageNumber += 1;
},
prevPage() {
this.pageNumber -= 1;
},
}
...
};
And my template:
<div v-for="poi in paginatedData" :key="poi.id">
<card :poi="poi"/>
</div>
Everything should work (and a page change does output the correct cards in the console) but my list is not updated even though the computed method is called on each click.
What is causing this issue? I've read it could be linked to a :key value missing, but it's there, and no data is being updated directly and manually in the array, only sliced out.
First, try this change, just for sure, and let me know in comment it works or not.
export default {
...
computed: {
paginatedData() {
...
const end = start + this.size - 1;
...
},
...
};
And yes: instead of id, try to use index:
<div v-for="(poi, idx) in paginatedData" :key="idx">
<card :poi="poi"/>
</div>

VueJS and lodash, filtered array displays empty unless main array is utilized in template

My mixin:
export default {
data() {
return {
charges: [],
catCharges: [],
offenses: ['Class I Offenses', 'Class II Offenses', 'Class III Offenses', 'Class IV Offense']
}
},
methods: {
getCharges() {
axios.get('admin/charges').then((response) => {
this.charges = response.data;
for(let offense = 1; offense <= this.offenses.length; offense++) {
this.catCharges[offense - 1] = this.chargesAtOffense(offense);
}
});
},
chargesAtOffense(offense) {
return _.filter(this.charges, { offense_level: offense });
}
},
created() {
this.getCharges();
}
};
Fetching data works, the array 'charges' gets populated with the following:
After populating the array, I start looping over the offenses array and filter all 'charges' from the main array into the 'catCharges' array, so all offenses are split into 4 separated arrays in that array.
Chrome's developer tools shows the array just fine and the charges are properly filtered.
This is my component:
<template>
<div>
<h1>Total charges: {{charges.length}}</h1>
<h1>Total offense categories: {{catCharges.length}}</h1>
<div v-for="(charges, offenseIdx) in catCharges">
{{charges}}
</div>
</div>
</template>
<script>
import chargesMixin from '../mixins/chargesMixin';
export default {
mixins: [chargesMixin],
data() {
return {
}
},
methods: {
},
computed: {
},
mounted() {
console.log('Disciplinary Segregation mounted.')
}
}
</script>
It uses the mixin provided above, and IT works and shows the catCharges array properly, HOWEVER when I remove the following line from the template:
<h1>Total charges: {{charges.length}}</h1>
The catCharges array is displayed as EMPTY, why do I need to use the charges array too along with the filtered array? This is driving me crazy.
I also tried the following method in the mixin which also causes the same issue:
chargesAtOffense(offense) {
var newCharges = [];
for(var i = 0; i < this.charges.length; i++) {
if(this.charges[i].offense_level != offense) continue;
const cloned = _.clone(this.charges[i]);
newCharges.push(cloned);
}
return newCharges;
}
I think your use case is linked to the reactivity system of VueJS.
https://v2.vuejs.org/v2/guide/reactivity.html
If you delete the line
<h1>Total charges: {{charges.length}}</h1>
you tell to VueJS to refresh your template only on catCharges get / set.
catCharges is an array, and so it's not as 'reactive' as a simple variable.
If you read precisely https://v2.vuejs.org/v2/guide/list.html#Caveats, prefer use a push on your catCharges to explain correctly to Vue that your array has changed.
I'll try this code :
getCharges() {
axios.get('admin/charges').then((response) => {
this.charges = response.data;
for(let offense = 1; offense <= this.offenses.length; offense++) {
this.catCharges.push(this.chargesAtOffense(offense));
}
});
},
Hope this will solve your problem.