Trigger a function if countdown reaches 0 - VueJS - javascript

I have created a countdown timer which decrease a number in the template perfectly, but now I need it to launch a function declared within methods after it reaches 0. I've tried to check if condition is met within methods but it doesn't launch anything when reaching 0.
Here is my index.vue
<template>
<div>
{{ timerCount }}
</div>
</template>
<script>
export default {
data(){
return{
timerCount: 60
}
},
watch: {
timerEnabled(value) {
if (value) {
setTimeout(() => {
this.timerCount = this.timerCount - 1;
}, 1000);
}
},
timerCount: {
handler(value) {
if (value > 0 && this.timerEnabled) {
setTimeout(() => {
this.timerCount = this.timerCount - 1;
}, 1000);
}
},
immediate: true
},
},
methods:{
launchThis() {
// I need this function to be launched if timerCount reaches 0
}
}
}
</script>
Any guidance to make it work will greatly appreciated.

You can use something like this
<script>
export default {
watch: {
timerCount: {
handler(value) {
if (value > 0 && this.timerEnabled) {
setTimeout(() => {
this.timerCount = this.timerCount - 1;
}, 1000);
} else {
this.launchThis() // run your function here
}
},
immediate: true
},
},
}
</script>

Related

How to start stopwatch from state time using Vue js

I have a stopwatch code using Vue 3 and Vuex that has functions that can start, stop and reset.
This is the code :
store/stopwatch/index.js
export default {
state: {
time: "00:10.000",
timeStarted: null,
timeBegan: null,
timeStopped: null,
stoppedDuration: 0,
started: null,
running: false,
temp: {
min: "0",
sec: "0",
ms: "0",
secondsPassed: 0
}
},
actions: {
start({ state, commit, dispatch }) {
if (state.running) return;
if (state.timeBegan === null) {
state.timeBegan = new Date();
}
if (state.timeStopped !== null) {
state.stoppedDuration += new Date() - state.timeStopped;
}
commit("start", {
callback: () => {
dispatch("clockRunning");
}
});
},
async clockRunning({ state, commit, dispatch }) {
let currentTime = new Date();
let timeElapsed = new Date(
currentTime - state.timeBegan - state.stoppedDuration
);
let min = timeElapsed.getUTCMinutes();
let sec = timeElapsed.getUTCSeconds();
let ms = timeElapsed.getUTCMilliseconds();
commit("newTemp", {
key: "secondsPassed",
value: parseInt(Math.abs((state.timeStarted - new Date()) / 1000), 10)
});
if (state.running) {
await dispatch("zeroPrefix", { num: min, digit: 2 }).then(
(zeroPrefixResponse) => {
commit("newTemp", {
key: "min",
value: zeroPrefixResponse
});
}
);
await dispatch("zeroPrefix", { num: sec, digit: 2 }).then(
(zeroPrefixResponse) => {
commit("newTemp", {
key: "sec",
value: zeroPrefixResponse
});
}
);
await dispatch("zeroPrefix", { num: ms, digit: 3 }).then(
(zeroPrefixResponse) => {
commit("newTemp", {
key: "ms",
value: zeroPrefixResponse
});
}
);
state.time =
state.temp.min + ":" + state.temp.sec + "." + state.temp.ms;
}
},
zeroPrefix(context, payload) {
return new Promise((resolve) => {
let zero = "";
for (let i = 0; i < payload.digit; i++) {
zero += "0";
}
resolve((zero + payload.num).slice(-payload.digit));
});
}
},
mutations: {
newTemp(state, payload) {
state.temp[payload.key] = payload.value;
},
addSecondPassed(state, second) {
state.temp.secondsPassed += second;
},
resetSecondPassed(state) {
state.temp.secondsPassed = 0;
},
start(state, payload) {
state.started = setInterval(() => {
payload.callback();
}, 10);
state.running = true;
},
stop(state) {
state.running = false;
state.timeStopped = new Date();
clearInterval(state.started);
},
reset(state) {
state.running = false;
clearInterval(state.started);
state.stoppedDuration = 0;
state.timeBegan = null;
state.timeStopped = null;
state.time = "00:10.000";
}
},
getters: {}
};
App.vue
<template>
<div>
<span class="time">{{ $store.state.stopwatch.time }}</span>
<br />
<button #click="start">Start</button>
<button #click="stop">Stop</button>
<button #click="reset">Reset</button>
</div>
</template>
<script>
export default {
methods: {
start() {
this.$store.dispatch("start");
},
stop() {
this.$store.commit("stop");
},
reset() {
this.$store.commit("reset");
},
},
};
</script>
This is the demo code on codesandbox
What happens with the code above by starting from the 10 seconds and then clicking the start button, seconds starting from the number 0 then 1,2,3. do not continue from number 10.
How to start stopwatch from state time with 10 seconds?
So when click the start button, seconds continue from 10 then to 11,12,13 and so on.
Looks like the issue is that the 10s is not added to the timeBegan. If you subtract the 10 seconds, it should work.
if (state.timeBegan === null) {
state.timeBegan = new Date() - 10_000;
}
On a side note, a mutation should be limited to just the mutation
This code is adding and removing interval from within the mutation and goes against how mutations are supposed to work.
start(state, payload) {
state.started = setInterval(() => {
payload.callback();
}, 10);
state.running = true;
},
stop(state) {
state.running = false;
state.timeStopped = new Date();
clearInterval(state.started);
},
And executing a callback from the mutation is also not good. Not that it won't work, but it's creating a weird state flow that goes against the principles of how the store works.

How can I stop the current swipe on a trackpad

We are developing a website with unique navigation. Part of it involves on each scroll either up or down, it fires JavaScript and navigates to a different HTML element. It is in Vue.js / Nuxt.
So far, everything works beautifully, minus the usage of the trackpad. The initial 2-finger swipe with the trackpad works -- however, this seems to initiate some sort of a smooth scroll, which takes a while to complete. If you try to 2-finger swipe again in the same direction, it's treated as one long scroll, which doesn't fire the JavaScript to advance to the next page. I did a console log of the deltaY and since it's a 2-finger swipe (smooth scroll?), the deltas take a second or two to finish.
This causes issues since you can't use the trackpad and swipe through sections quickly. You have to swipe down, wait until the scroll finishes, then swipe down again.
How would we fix this issue? Is there a way to kill the current scroll, or to eliminate smooth scrolling and just have scrolls go 100 deltas in one way or the other?
Thanks in advance
Vue.js code
export default {
layout: 'empty',
components: {
Footer,
Logo,
LottieAnimation,
ServiceIcon,
CloseBtn
},
async asyncData({ app }) {
try {
return await app.$api.$get('/services.json')
} catch (error) {
return {
error
}
}
},
data() {
return {
activePage: 0,
config: {},
error: null,
goToPageTimer: null,
isTouchpad: null,
page: {},
scrollDisabled: false,
scrollPercentage: 0,
scrollPercentageOld: 0,
scrollDirection: null,
scrollTimer: null,
touchStart: null,
touchTimer: null,
lastWheelDirection: null,
lastWheelEvent: null,
wheelEventsCount: 0,
wheelEventsLimit: 25,
wheelStopTime: 120
}
},
watch: {
scrollPercentage(newValue, oldValue) {
this.scrollDirection = newValue > oldValue ? 'bottom' : 'top'
if (this.scrollDirection === 'bottom') {
this.goToNextPage()
} else {
this.goToPreviousPage()
}
}
},
mounted() {
window.addEventListener('keydown', this.onKeyDown)
window.addEventListener('scroll', this.handleScroll)
window.addEventListener('wheel', this.onMouseWheel)
window.addEventListener('touchend', this.onTouchEnd)
window.addEventListener('touchstart', this.onTouchStart)
this.initPage()
},
destroyed() {
window.removeEventListener('keydown', this.onKeyDown)
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('wheel', this.onMouseWheel)
window.removeEventListener('touchend', this.onTouchEnd)
window.removeEventListener('touchstart', this.onTouchStart)
},
methods: {
disableScrolling() {
this.scrollDisabled = true
if (this.goToPageTimer) {
clearTimeout(this.goToPageTimer)
}
this.goToPageTimer = setTimeout(() => {
this.scrollDisabled = false
}, 30)
},
getServicePageId(slug) {
let servicePageId = null
Object.keys(this.page.childPages).forEach((pageId) => {
if (this.page.childPages[pageId].slug === slug) {
servicePageId = parseInt(pageId)
}
})
return servicePageId
},
goToNextPage() {
if (
this.scrollDisabled ||
this.activePage === this.page.childPages.length
) {
return
}
this.activePage = this.activePage === null ? 0 : this.activePage + 1
if (this.activePage < this.page.childPages.length) {
this.scrollToActivePage()
}
},
goToPreviousPage() {
if (this.scrollDisabled) {
return
}
if (!this.activePage) {
return
}
this.activePage -= 1
this.scrollToActivePage()
},
goToPage(index) {
if (this.scrollDisabled) {
return
}
this.activePage = index
this.scrollToActivePage()
},
handleScroll() {
// If scrolling to top do nothing
if (!window.scrollY) {
return
}
if (this.activePage < this.page.childPages.length - 1) {
window.scrollTo(0, 0)
}
},
initPage() {
if (this.$route.query.service) {
this.activePage = this.getServicePageId(this.$route.query.service)
}
},
isServiceActiveOrIsLatestService(slug) {
const servicePageId = this.getServicePageId(slug)
// Service is active
if (this.activePage === servicePageId) {
return true
}
const latestServicePageId = this.page.childPages.length - 1
// Service is the latest and active page is over it (user is looking the footer)
return (
servicePageId === latestServicePageId &&
this.activePage > latestServicePageId
)
},
onKeyDown(e) {
const nextPageKeys = [
34, // Page down
39, // Arrow right
40 // Arrow down
]
const previousPageKeys = [
33, // Page up
37, // Arrow left
38 // Arrow up
]
if (nextPageKeys.includes(e.keyCode)) {
this.goToNextPage()
} else if (previousPageKeys.includes(e.keyCode)) {
this.goToPreviousPage()
}
},
onMouseWheel(event) {
const now = +new Date()
const millisecondsSinceLastEvent = this.lastWheelEvent
? now - this.lastWheelEvent
: 0
const eventsLimitReached = this.wheelEventsCount > this.wheelEventsLimit
const stopTime = this.wheelStopTime
const delta = Math.sign(event.deltaY)
const directionChanged = delta !== 0 && this.lastWheelDirection !== delta
this.lastWheelEvent = now
if (directionChanged) {
this.lastWheelDirection = delta
}
if (
!directionChanged &&
!eventsLimitReached &&
millisecondsSinceLastEvent &&
millisecondsSinceLastEvent <= stopTime
) {
this.wheelEventsCount += 1
return
}
this.wheelEventsCount = 0
if (delta === -1) {
this.goToPreviousPage()
} else {
this.goToNextPage()
}
},
onTouchEnd(e) {
const now = +new Date()
const touchEndX = e.changedTouches[0].clientX
const touchEndY = e.changedTouches[0].clientY
// Try to guess single touch based on touch start - touch end time
const isSingleTouch = now - this.touchTimer <= 100
// Single touch
if (isSingleTouch && this.touchStart.x === touchEndX) {
const horizontalPercentage = Math.ceil(
(touchEndX * 100) / window.innerWidth
)
const verticalPercentage = Math.ceil(
(touchEndY * 100) / window.innerHeight
)
if (horizontalPercentage <= 40) {
this.goToPreviousPage()
return
}
if (horizontalPercentage >= 60) {
this.goToNextPage()
return
}
if (verticalPercentage <= 40) {
this.goToPreviousPage()
return
}
if (verticalPercentage >= 60) {
this.goToNextPage()
return
}
}
// Touch move
if (this.touchStart.y > touchEndY + 5) {
this.goToNextPage()
} else if (this.touchStart.y < touchEndY - 5) {
this.goToPreviousPage()
}
},
onTouchStart(e) {
this.touchTimer = +new Date()
this.touchStart = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
}
},
onVisibilityChanged(isVisible, entry) {
if (isVisible) {
entry.target.classList.add('dynamic-active')
} else {
entry.target.classList.remove('dynamic-inactive')
}
},
scrollToActivePage() {
this.scrollToService(this.page.childPages[this.activePage].slug)
},
scrollToRef(ref) {
if (!this.$refs[ref]) {
return
}
if (this.$refs[ref][0]) {
this.$scrollTo(this.$refs[ref][0], 100, {
force: true,
cancelable: false
})
} else {
this.$scrollTo(this.$refs[ref], 100, { force: true, cancelable: false })
}
this.disableScrolling()
},
scrollToService(slug) {
this.scrollToRef(`service-${slug}`)
},
serviceBgImageUrl(service) {
return require(`~/assets/img/services/backgrounds/${service.slug}.jpg`)
}
},
head() {
const data = {
bodyAttrs: {
class: 'services'
}
}
if (this.page.data) {
data.title = this.page.data.title
data.meta = this.page.metadata
}
return data
}
}

Display currentTime of a song in vue

I'd like to know how can I make time constantly update itself. So when I press the play button the seconds start to update automatically from 0:00 to the end , because now it just updates onclick. I am trying to use HTML5 audio and I have successfully managed to get the time updating as a label from this line of code:
sound.ontimeupdate = function () { document.getElementById('Time').innerHTML = sound.currentTime.toFixed() }
But thats not what I need, I would like to get the time attribute in data() to get updated and displayed in the label as shown in my HTML code.
I tried adding an event listener but it did not work... It gets called and every call was logged with console.log but time attribute was not updated
let sound = null
export default {
data () {
return {
isPlaying: false,
time: 0,
duration: 0
}
},
methods: {
playMusic () {
if (!sound) {
sound = new Audio(require('assets/YES.mp3'))
}
this.isPlaying = true
sound.play()
// sound.addEventListener('timeupdate', function () { this.time = sound.currentTime.toFixed() }) -- did not work
this.time = sound.currentTime.toFixed()
}
Html:
<label id="Time" #timeupdate>
{ { time } }:{ { duration } }
</label>
Inside your addEventListener you get a different this than you might expect.
Either use fat arrow
sound.addEventListener('timeupdate', () => this.time = sound.currentTime.toFixed() )
or, the old way, save this
let that = this
sound.addEventListener('timeupdate', function () { that.time = sound.currentTime.toFixed() })
you could just add a generic timer dynamically. You can use a watch to add/remove it like so:
(untested code)
export default {
data() {
return {
isPlaying: false,
time: 0,
duration: 0,
intervalId: null,
sound: null
};
},
watch: {
isPlaying(isPlaying) {
if (this.intervalId !== null) {
clearInterval(this.intervalId);
}
if (isPlaying) {
this.sound.play();
this.intervalId = setInterval(() => {
this.time = this.sound.currentTime.toFixed();
}, 500);
} else {
this.sound.stop();
}
}
},
methods: {
playMusic() {
if (!this.sound) {
this.sound = new Audio(require("assets/YES.mp3"));
}
this.isPlaying = true;
}
}
};

Why is this data not correctly passing from parent component to child component, in vue.js?

I have a vue component responsible for pagination, so that only 20 items are shown per page. Everything is working except for the one value that I need passed down from the parent component, the 'count' value that returns the total number of items.
I use the 'count' value to calculate whether the "Next Page" button is active or not and what the total number of pages should be. Using console.log I can see that the 'count' value returns correctly in to the parent component, but it always comes back as undefined in the child pagination component. Please advise how I can fix this.
(I'm not sure if using watch here is correct; I thought it would solve the problem, but it changed nothing.)
EDIT: I've posted more of both the child and parent component for better context.
Here is the script of the child component:
<script>
export default {
name: 'Pagination',
props: ['count'],
watch: {
count: function (val, oldVal) {}
},
data () {
return {
currentPage: 1,
limit: 20,
paginationVisible: true,
firstPageIsCurrent: true,
prevEllipsisVisible: false,
pageLink1: 2,
pageLink1Visible: true,
pageLink1IsCurrent: false,
pageLink2: 3,
pageLink2Visible: true,
pageLink2IsCurrent: false,
pageLink3: 4,
pageLink3Visible: true,
pageLink3IsCurrent: false,
nextEllipsisVisible: true,
lastPageVisible: true,
lastPageIsCurrent: false,
prevDisabled: true,
nextDisabled: false,
lastPage: 1
}
},
methods: {
handlePrev () {
if (!this.prevDisabled) {
this.currentPage -= 1
this.paginate()
}
},
handleNext () {
if ((this.currentPage * this.limit) < this.count) {
this.currentPage += 1
this.paginate()
}
},
handleFirstPage () {
if (this.currentPage !== 1) {
this.currentPage = 1
this.paginate()
}
},
handlePageLink1 () {
if (this.currentPage !== this.pageLink1) {
this.currentPage = this.pageLink1
this.paginate()
}
},
handlePageLink2 () {
if (this.currentPage !== this.pageLink2) {
this.currentPage = this.pageLink2
this.paginate()
}
},
handlePageLink3 () {
if (this.currentPage !== this.pageLink3) {
this.currentPage = this.pageLink3
this.paginate()
}
},
handleLastPage () {
if (this.currentPage < this.lastPage) {
this.currentPage = this.lastPage
this.paginate()
}
},
paginateAdjust () {
console.log(this.count)
// adjust pagination bar and previous/next buttons
this.nextDisabled = ((this.currentPage * this.limit) >= this.count)
this.prevDisabled = (this.currentPage === 1)
const pageCount = Math.ceil(this.count / this.limit)
// console.log(pageCount + ', cp: ' + this.currentPage)
if (pageCount === 1) {
this.paginationVisible = false
} else {
this.paginationVisible = true
// first page link
this.firstPageIsCurrent = this.currentPage === 1
// previous ellipsis
this.prevEllipsisVisible = this.currentPage > 3 && pageCount > 5
// first page link
this.pageLink2Visible = pageCount > 2
if (this.currentPage < 4) {
this.pageLink1 = 2
} else if ((pageCount - this.currentPage) < 3) {
this.pageLink1 = pageCount - 3
} else {
this.pageLink1 = this.currentPage - 1
}
this.pageLink1IsCurrent = this.pageLink1 === this.currentPage
// second page link
this.pageLink2Visible = pageCount > 3
if (this.currentPage < 4) {
this.pageLink2 = 3
} else if ((pageCount - this.currentPage) < 3) {
this.pageLink2 = pageCount - 2
} else {
this.pageLink2 = this.currentPage
}
this.pageLink2IsCurrent = this.pageLink2 === this.currentPage
// third page link
this.pageLink3Visible = pageCount > 4
if (this.currentPage < 4) {
this.pageLink3 = 4
} else if ((pageCount - this.currentPage) < 3) {
this.pageLink3 = pageCount - 1
} else {
this.pageLink3 = this.currentPage + 1
}
this.pageLink3IsCurrent = this.pageLink3 === this.currentPage
// next ellipsis
this.nextEllipsisVisible = ((pageCount - this.currentPage) >= 3) && pageCount > 5
// last page
this.lastPage = pageCount
this.lastPageIsCurrent = this.currentPage === pageCount
}
},
emitMSQ () {
this.$emit('msq', this.currentPage, this.limit)
},
paginate () {
this.paginateAdjust()
this.emitMSQ()
}
},
created () {
this.paginate()
}
}
</script>
Here is the (relevant) script from the parent component:
export default {
name: 'index',
components: {Pagination},
computed: {
apps () {
return this.$store.state.apps
}
},
data () {
return {
apps: [],
count: 0,
searchAddress: ''
}
},
methods: {
onSearch () {
if (this.searchAddress.length === 12 || this.searchAddress.length === 0) {
this.currentPage = 1
this.makeServerQuery()
}
},
makeServerQuery (cp, pp) {
let queryStr = '?uid=' + this.$auth.user().id + '&cp=' + cp + '&pp=' + pp
if (this.searchAddress.length === 12) {
queryStr += '&se=' + this.searchAddress
}
this.$http.get('/apps' + queryStr).then(res => {
this.apps = res.data.apps
this.count = res.data.count
console.log(this.count + ' msq()')
})
},
The emit, server query, and actual pagination elements all work fine. But my next page button is wrong and the last page button displays NaN because it can't run the correct calculations when count === undefined.
Thanks in advance for the help.
From your comments, it looks like you are not really passing anything to your component's prop.
As #Dan suggested, your count prop is undefined because nothing is being passed to it from the parent. To pass the count data property from the parent to the child you need to use a template binding <child v-bind:count="count"></child> or its shorthand <child :count="count"></child>.
The official documentation is a good starting point for understanding props.
Here's a minimal example.
Vue.config.productionTip = false;
Vue.component('child', {
template: `
<div class="child">
from parent: {{count}}
<br>
<button :disabled="!enableNext" #click="onNext">Next</button>
</div>
`,
props: ['count'],
computed: {
enableNext() {
return this.count && this.count > 0;
}
},
methods: {
onNext() {
console.log('Go to page ' + this.count + 1)
}
}
})
new Vue({
el: '#app',
template: `
<div class="parent">
Parent count: {{ count }}
<child :count="count" />
<br>
<button #click="count++">Increment count</button>
</div>
`,
data:() => ({
count: 0
})
});
div { border: solid 1px black; padding: 1em; }
.child { background: lightgray; }
button:disabled, button[disabled]{
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Vue scope: how to delay handling of #mouseover

So I want to have an action only if the user has the mouse on the div for at least 1 second. Inside template:
<div #mouseover="trigger"></div>
Inside script:
data() {
return {
hovered: false
}
}
methods: {
trigger() {
setTimeout(function(){ this.hovered = true }, 1000)
}
}
My problem is that I don't understand the scope of Vue. Because this.hovered is inside another function, it does not find the actual hovered data variable. What's the solution to this?
Have you tried using an arrow function in your setTimeout? It will maintain this.
data() {
return {
hovered: false
}
}
methods: {
trigger() {
setTimeout(() => { this.hovered = true }, 1000)
}
}
<div #mouseover="activarOver" #mouseleave="resetOver "> eventos </div>
data: () => {
return {
countMouseOver: 0
}
},
methods: {
activarOver () {
this.countMouseOver++
if (this.countMouseOver < 2) {
this.setMostrarPopup()
}
console.log(this.countMouseOver)
},
resetOver () {
this.countMouseOver = 0
console.log('reset')
},
}
Implementation to show when hovered over for 1 second, then disappear when no longer hovered.
<span #mouseover="hover" #mouseleave="unhover">Hover over me!</span>
<div v-if="show">...</div>
data() {
return {
show: false;
hovering: false,
};
},
methods: {
hover() {
this.hovering = true;
setTimeout(() => this.show = this.hovering, 1000);
},
unhover() {
this.hovering = false;
this.show = false;
},
}
I have been solving this problem for selecting items in a list only if the user hovers for some time (to prevent menu flickering)
Template (pug):
.div(
#mouseover="select(identifier)"
#mouseout="clear()"
) {{content}}
Data:
hovered: false
selectedId: ""
and the methods
select(identifier) {
this.selectedId = identifier
setTimeout(() => {
if(this.selectedId === identifier )
this.hovered = true
},
1000
)
},
clear() {
this.selectedId = ''
}
the approach is to check if whatever user is hovering on is the same as they were hovering on a second ago.
If you want to use old syntax, bind 'this' to the setTimeout function
data() {
return {
hovered: false
}
}
methods: {
trigger() {
setTimeout(function(){ this.hovered = true }.bind(this), 1000)
}
}

Categories

Resources