So I have a vuetify simple table that displays available times to book appointments. However, this times are pull from a database and that information get changes every 5 minutes (based on people that booked or cancel). The user will need to refresh the table to get the latest changes. Im trying to introduce some sort of auto refresh in VueJs that reloads the data every 5 minuts. this is my method that is been called right now
created(){
this.fetchAvailableTimeSlotsData75();
},
method:{
fetchAvailableTimeSlotsData75() {
this.$axios.get('appointments75', {
params: {
date: this.isCurrentMonth(this.strSelectedDate) ? '' : this.strSelectedDate,
week: this.intPageNumber
}
})
.then((objResponse) => {
if(objResponse.status == 200){
// console.log(objResponse.data)
this.total = objResponse.data.total;
this.arrAvailableDates = objResponse.data.dates;
this.arrAppointmentsData = objResponse.data.data;
this.getAppointments();
}
})
.catch((objError) => {
})
.finally(() => {
this.blnLoading = false;
this.snackbar = false
});}
}
Whats the best way to approach this in VueJs? Any Ideas?
To put it simply, use setInterval:
var _timerId;
export default {
data: () => ({
pollingInterval: 1000 * 60 * 5
}),
created() {
this.startPolling(true);
},
methods: {
startPolling(init = false) {
if (init) {
// Call it immediately
this.fetchAvailableTimeSlotsData75();
this.startPolling();
return;
}
_timerId = setInterval(this.fetchAvailableTimeSlotsData75, this.pollingInterval);
}
},
// Optional
destroyed() {
clearInterval(_timerId);
}
}
Related
I dont know much about vue/bootstrap and reading docs does not help me to understand how it all works.
How to open a modal that is created after the page was loaded. From user input. User clicks button then the modal loads into a list prop and then renders into DOM and then it opens up.
Im at the point where i created event when user clicks the button that loads the modal into the list, but how do you catch the "modal has been added to DOM" event and then you can use getElementByID to instantiate the modal and then use .show() to show it?
I can see that the card that supposed to render on the page loads/renders, but the method get null. Im guessing that the method runs before the page/DOM has been re-rendered. So how do you run another method with parameter of sorts after the custom event that added the item to list has been triggered?
The code is too big and convoluted to post. But if need be i could try to trim it down, but its a mess.
App.vue
<template>
<div class="container-center">
<AnimeList />
</div>
</template>
AnimeList.vue
<template>
<div class="containerlist">
<AnimeCardModal
v-for="anime in animeList"
:anime="anime"
#checkAnimeListForRelatedEvent="checkAnimeListForRelated"
/>
</div>
</template>
<script setup>
import { defineComponent } from "vue";
import AnimeCardModal from "./AnimeCardModal.vue";
import axios from "axios";
</script>
<script>
export default defineComponent({
name: "AnimeList",
data() {
return {
animeList: [],
limit: 30,
page: 1,
reachedEnd: false,
};
},
methods: {
async getAnimeLsit() {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
this.animeList = res.data.data;
this.page = res.data.next.page;
this.limit = res.data.next.limit;
},
async getNextBatch() {
let bottomOfWindow =
document.documentElement.scrollTop + window.innerHeight ===
document.documentElement.offsetHeight;
if (bottomOfWindow && !this.reachedEnd) {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
res.data.data.map((item) => {
this.animeList.push(item);
});
if (!res.data.next) {
this.reachedEnd = true;
} else {
this.page = res.data.next.page;
this.limit = res.data.next.limit;
}
}
},
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
}
}
// Add the anime to the list
},
},
created() {
window.addEventListener("scroll", this.getNextBatch);
},
deactivated() {
window.removeEventListener("scroll", this.getNextBatch);
},
async mounted() {
await this.getAnimeLsit();
},
components: {
AnimeCardModal,
},
});
</script>
Here is the method that gets triggered by the user click event where it loads the Not in main list data and should render on page/DOM.
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]); <--------------------------------------
console.log("added to list");
}
}
// Add the anime to the list
},
The added item is a modal with element id. I want to instantiate this element as new Modal() and open it with .show().
But the i get error that the element does not exist = null and i cant get it, but i can see it on screen.
EDIT:1
Ok so like as per usual, once i post on SO i find an answer to my problem, but it turns into another problem.
SO to get the rendered element i used this:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => { <----------------------
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
}else{
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
// Add the anime to the list
},
It works, but now the modals overlay each other, seems like its not working like when you add the attributes to the card element that opens modal:
:data-bs-target="'#anime-card-modal-' + anime.id"
data-bs-toggle="modal"
Is there a way to get the same effect from method as with these attributes?
I want to open a modal, by clicking an element with those attributes, then when i click another element with them attributes (different target id) it closes previously opened modal and opens the target modal.
Alright, i found a solution, works pretty good.
Instead of using myModal.show() i used myModal.toggle("anime-card-modal-" + animeID) and the else statement is not needed in the event method:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => {
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.toggle("anime-card-modal-" + animeID) <---------------
}
}
// Add the anime to the list
},
form(#submit.prevent="onSubmit")
input(type="text" v-model="platform" placeholder="Add platform name...")
input(type="submit" value="Submit" class="button" #click="clicked = true")
button(type="button" value="Cancel" class="btn" #click="cancelNew") Cancel
h3(v-if="clicked") Thank you for adding a new platform
span {{ countdown }}
This is my template and when the user submits the form, I want to count down from 3 using setTimeout function and submit after 3 seconds.
If I have it this way, it works;
data() {
return {
countdown: 3,
platform: ""
}
},
methods: {
countDownTimer() {
setTimeout(() => {
this.countdown -= 1
this.countDownTimer()
}, 1000)
},
onSubmit() {
let newplatform = {
name: this.platform
}
this.addPlatform(newplatform)
this.platform = ' '
this.countDownTimer()
}
}
However I have 3 more forms and I didn't want to repeat the code. So I wanted to put countdown in the store,
countDownTimer({commit}) {
setTimeout(() => {
countdown = state.countdown
countdown -= 1
commit('COUNTDOWN', countdown)
this.countDownTimer()
}, 1000)
}
and mutate it like
COUNTDOWN(state, countdown) {
state.countdown = countdown
}
This doesn't work and I am not sure If I am able to change the state, commit the changes inside of settimeout function? Is there a better way I can implement this?
The issues:
The recursive setTimeout isn't stopped.
The countdown timer isn't reset.
Use setInterval (and clearInterval) instead of the recursive setTimeout.
For async logic including setTimeout, use an action rather than a mutation.
Include state from the context object (where you get commit), or it will be undefined.
Try this:
actions: {
countDownTimer({ state, commit, dispatch }) { // state, commit, dispatch
commit('RESET');
const interval = setInterval(() => { // Use `setInterval` and store it
commit('COUNTDOWN');
if (state.countdown === 0) {
clearInterval(interval); // Clear the interval
dispatch('updateDatabase'); // Call another action
}
}, 1000)
}
}
mutations: {
RESET(state) {
state.countdown = 3;
},
COUNTDOWN(state) {
state.countdown--;
}
}
Is it possible to have a global variable in store? I want to create/remove an interval I use for polling an API. Right now I do the following:
const refreshData = null;
export default {
namespaced: true,
state: {
...
},
mutations: {
...
},
actions: {
pollForData() {
// Make API call
.
.
.
// Set interval to poll every 5 seconds if the data has a certain flag
if(res.data.update) {
if(!refreshData) {
refreshData = setInterval(() => {
dispatch('pollForData');
});
}
} else {
if(refreshData) {
clearInterval(refreshData);
refreshData = null;
}
}
}
}
}
This works perfectly, but the variable lying outside is bothering me. Because I don't know if this is the right way to do it, and I think there must be a better way.
I have never used Firebase before this is my first stab at it using Vue.
I have a setup Firebase using Realtime Databas and set up my project so I can post using the below code in my .vue file
this.$http.post('https://MY_PROJECT_NAME.firebaseio.com/posts.json', {
title: this.blog.title,
body: this.blog.content,
createdDate: this.$options.filters.fullMthDate(this.blog.publishDate),
author: this.blog.author,
active: true,
closedDate: null,
}).then((response) => {
this.$blogAdded = true;
this.loading = false;
this.$router.push('/');
}).catch((error) => {
console.log(error);
});
The thing I can't seem to find an answer to is how to then update this document when needed (e.g. user deletes an item, I want 'active' to become false)
I went for the above code as I was using net ninjas tutorials who set FireBase up this way.
I then do a get to list all items using below in my main component
this.$http.get('https://MY_PROJECT_NAME.firebaseio.com/posts.json').then(function(data) {
return data.json();
}).then(function(data) {
var blogsArray = [];
for (let key in data) {
const date = new Date(data[key].createdDate);
const todaysDate = new Date();
if (date <= todaysDate) {
data[key].id = key
blogsArray.push(data[key])
}
}
this.blogs = blogsArray;
this.loading = false;
});
And this displays them on my site
When the user clicks the tile they go to a page where they can 'Delete/Cancel' the post and it's here I am stuck. Below is the code I am using for displaying the selected item
data() {
return {
id: this.$route.params.id,
blog: {},
loading: false,
closeModal: false,
showModal: false
};
},
beforeMount() {
this.loading = true;
},
created() {
this.$http.get('https://MY_PROJECT_NAME.firebaseio.com/posts/' + this.id + '.json').then(function(data) {
return data.json();
}).then(function(data) {
this.blog = data;
this.loading = false;
});
},
methods: {
showCloseBlogModal() {
console.log(this.blog)
VueEvent.$emit('show-delete-blog-modal', this.blog);
}
}
Then when the modal is displayed I get the following in the console.log
I need to update the 'active' value to false when they click 'Yes' using the below
methods: {
deleteBlog() {
// CODE HERE WHEN CLICK 'YES' TO CANCEL
}
}
I have Zabuto calendar showing booking dates, but some tours have many dates for the period, and loading them is slow.
I have changed the API to paginate the data, and send a next url if there is more data to load, but I can't see how to get zabuto calendar to update its data once it is displayed, and with Javascript being the ultimate asynchronous programming language, I figured there must be a way the calendar can display and upload data at the same time.
Posts such as this
How to load data from ajax to zabuto calendar plugin?
shows how to load the calendar data by ajax call, but not how to continuously upload more data asynchronously while the current calender data is displayed. Other posts indicate that the only way is to reload the entire calendar
reloading AJAX data for Zabuto Calendar after modal dismissal. I would prefer an asynchronous way.
The previous developer started to use the Vue framework, So what I have is a Zabuto calendar Vue module
<template>
<div id="my-calendar-a"></div>
</template>
<script>
import Vue from 'vue'
import moment from 'moment'
import { mapGetters } from 'vuex'
export default {
name: 'ZabutoCalendar',
methods: {
initialise: function () {
$('.calendar-month-navigation .glyphicon').click(function () {
Vue.bus.$emit('calendar-change-month')
})
}
},
props: ['tour'],
computed: {
...mapGetters('cart', [
'cartItems'
]),
...mapGetters('calendar', [
'tourDates'
])
},
mounted: function () {
var self = this
var currentDate = new Date()
/*
Use of the thrid party plugin zabuto calendar to
set up the calendar and check if dates are being clicked
https://github.com/zabuto/calendar
*/
var nextUrl = '/api/check-dates?year=' + moment().format('YYYY') +
'&month=' + moment().format('M') + '&tour=' + self.tour;
this.$store.dispatch('calendar/getTourDates', nextUrl).then(response => {
// I tried putting a while nextUrl loop here, but the calender wont display till dispatch returns
nextUrl = self.tourDates[1].next_url;
$(self.$el).zabuto_calendar({
data: self.tourDates[0].tourdates,
weekstartson: 0,
show_previous: false,
year: currentDate.getFullYear(),
month: currentDate.getMonth() + 1,
action: function () {
if ($(this).find('> div').hasClass('start_spots')) {
// reconstruct data
var selectedTour = {}
var id = this.id
var elem = $('#' + id)
$('.calendar-dow .selected').removeClass('selected')
$(this).find('> div').addClass('selected')
selectedTour = _.find(self.tourDates[0].tourdates, { 'tour_date_id': elem.data('tour_date_id') })
selectedTour['styled_date'] = moment(elem.data('date')).format('Do MMMM YYYY')
if ($(this).find('> div').hasClass('start_future')) {
selectedTour['available'] = 1
for (var index in self.cartItems) {
if (self.cartItems[index].date === elem.data('date')) {
selectedTour['available'] = 3
break
}
}
} else {
selectedTour['available'] = 2
}
self.$store.commit('calendar/setSelectedTour', selectedTour)
Vue.bus.$emit('date-click')
}
}
})
// while loop could surround above code
})
}
}
</script>
And a javascript module to do the ajax call to get all the data in one go
import axios from 'axios'
import moment from 'moment'
export const calendar_module = {
namespaced: true,
state: {
tourDates: [],
selectedTour: {}
},
getters: {
tourDates: (state) => {
return state.tourDates
},
selectedTour: (state) => {
return state.selectedTour
}
},
mutations: {
setSelectedTour (state, selectedTour) {
state.selectedTour = selectedTour
},
setTourDates (state, tourDates) {
state.tourDates = tourDates
}
},
actions: {
getTourDates ({ commit }, datesUrl) {
var response_data = axios.get(datesUrl).then((response) => {
commit('setTourDates', response.data)
});
return response_data;
}
}
}
The API response data is returned in the form
{ 'tourdates': array_data_object, 'next_url', url_string }
with next_url (within the response) set to an empty string if there is no more data. getTourDates actually returns the API response. I tried putting a while nextUrl loop around the code where commented, but zabuto calendar does not display till the dispatch function returns.
Does Zabuto Calendar have a built in way to asynchronously update its data while displaying? Otherwise how else can I get it to asynchronously display and load future dates?
Another way would be to get the ajax call to run several concurrently, and just return null in any that are redundant, but I would prefer to query the database first to see how many pages are needed, and would prefer not to waste an ajax call just to find out how many asynchronous hits are needed to get all data.
I could not put in a while nextUrl loop, so tried axios.all() instead, which allows for asynchronous calling of multiple gets at the same time. This made no improvement of load time, which was probably just as well, because it forced me to look at my REST API which had several inefficiencies that I would not have otherwise cleaned up.
This is my Async solution (which I no longer needed to use once the API was nice and quick) for posterity
import axios from 'axios'
import moment from 'moment'
export const calendar_module = {
namespaced: true,
state: {
tourDates: [],
selectedTour: {}
},
getters: {
tourDates: (state) => {
return state.tourDates
},
selectedTour: (state) => {
return state.selectedTour
}
},
mutations: {
setSelectedTour (state, selectedTour) {
state.selectedTour = selectedTour
},
setTourDates (state, tourDates) {
if (state.tourDates.length == 0) {
state.tourDates = tourDates[0].tourdates;
} else {
state.tourDates = state.tourDates.concat(tourDates[0].tourdates);
}
}
},
actions: {
getTourDates ({ commit }, datesUrl) {
var response_data = axios.all([
axios.get(datesUrl + '&page=1'),
axios.get(datesUrl + '&page=2'),
axios.get(datesUrl + '&page=3'),
axios.get(datesUrl + '&page=4'),
axios.get(datesUrl + '&page=5'),
axios.get(datesUrl + '&page=6')
]).then(axios.spread(function (response1, response2, response3, response4, response5, response6) {
commit('setTourDates', response1.data);
commit('setTourDates', response2.data);
commit('setTourDates', response3.data);
commit('setTourDates', response4.data);
commit('setTourDates', response5.data);
commit('setTourDates', response6.data)
}));
return response_data;
}
}
}