I have a v-textarea, I write something to area and if I do not any action, in v-textarea context must be cleared automatic after a certain time (2 minutes). How can I do it?
<v-textarea
v-model.trim="text"
clearable
:label="modeLabel"
>
I would do something like this with a timer.
<v-textarea
v-model.trim="text"
clearable
:label="modeLabel"
#change="clearHandler(event)"
>
var timerID = null;
function clearHandler() {
if (timerID) {
clearTimeout(timerID);
}
// create a timer to clear the textarea by setting the model to empty string
timerID = setTimeout(() => {
this.text = "";
}, 120000);
}
Also, I would use this.timerID or a ref variable, instead of var like my example, depending on what version of vue you are using.
I've created this with a simple watcher. First, we create a function that includes the timeout. After that we create a watcher triggered to val. In this watcher, we call the timeout and after that clear the timeout. The reason for this is that we want it to re-run the function. The watcher also watches the value, if the user enters sth, the timeout will be canceled.
new Vue({
el: "#app",
data() {
return {
val: ''
}
},
methods: {
timeout() {
return setTimeout(() => {
this.val = ''
}, 5000)
}
},
watch: {
val: {
handler() {
this.timeout()
clearTimeout(this.timeout);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="val" />
</div>
Related
I'm having lots of elements on which #mouseenter set a value to true and #mouseleave sets it to false. Basically what I need is a way to set a reactive variable to true if the mouse hovers the element.
I've been trying to figure out how to write such custom directive from the docs but it only mentions how to use .focus() js function on an element. Which js functions would be used for said directive?
Something like:
const vHover = {
mounted: (el) => {
el.addEventListener('mouseenter', state.hover=true)
el.addEventListener('mouseleave', state.hover=false)
}
}
I think you could do something like:
app.directive('hover', {
created(el, binding) {
const callback = binding.value
el.onmouseenter = () => callback(true)
el.onmouseleave = () => callback(false)
},
unmounted(el) {
el.onmouseenter = null
el.onmouseleave = null
}
})
Template:
<button v-hover="onHoverChange">Example</button>
Methods:
onHoverChange(isHovered) {
console.log(isHovered)
}
I believe this is not the intended use of directives. The value of the state cannot be mutated within the directive. You can pass the variable through the binding, but you cannot update it.
binding: an object containing the following properties.
value: The value passed to the directive. For example in v-my-directive="1 + 1", the value would be 2.
oldValue: The previous value, only available in beforeUpdate and updated. It is available whether or not the value has changed.
so if you do el.addEventListener('mouseenter', binding.hover=true), as you may have noticed, it will not update the state.
However, if we use the internals (PSA: though not recommended since they could potentially change at any time), you could get instance using the vnode, and use the binding.arg to denote which Proxy (state)
so you could get the reactive variable with vnode.el.__vueParentComponent.data[binding.arg]
<script>
export default {
data(){
return {
state: { hover:false }
}
},
directives: {
hover: {
mounted(el, binding, vnode) {
el.addEventListener('mouseenter', () => {
vnode.el.__vueParentComponent.data[binding.arg].hover = true
})
el.addEventListener('mouseleave', () => {
vnode.el.__vueParentComponent.data[binding.arg].hover = false
})
},
}
}
}
</script>
<template>
<h1 v-hover:state="state">HOVER {{ state }}</h1>
</template>
SFC playground link
of course you might want to add the unmounted and even consider adding mouseleave dynamically only when mouseenter fires
This is how it can be done inside the component:
const vHover = {
mounted: (el) => {
el.addEventListener('mouseenter', () => {state.hover=true})
el.addEventListener('mouseleave', () => {state.hover=false})
},
unmount: (el) => {
el.removeEventListener('mouseenter', () => {state.hover=true})
el.removeEventListener('mouseleave', () => {state.hover=false})
}
}
I have a child component that emits a value, and in the parent I perform an axios call with this value each time it is emitted. My problem is that I want to trigger the axios call only if in x ms (or seconds) the child has not emmited another value in order to reduce the amount of calls I do.
Code here :
<script>
import axios from "axios";
import DataTable from './DataTable.vue';
export default {
name: 'Test',
data() {
return {
gamertags: [],
// Utils
timeout: 500,
delay: 500
}
},
methods: {
// API calls
async getGamerTags(string='') {
const path = `http://localhost:5000/gamertags?string=${string}`
await axios.get(path)
.then((res) => {
this.gamertags = res.data;
})
.catch((error) => {
console.error(error);
});
},
// DataTable
handleFilters(filters) {
clearTimeout(this.timeout);
this.timeout = setTimeout(this.getGamerTags(filters.find(o => o.field == "playerGamerTag").filter), this.delay);
}
}
components: {
DataTable
}
};
</script>
<template>
<DataTable
#filters="handleFilters"
/>
</template>
Thanks in advance.
What you need is debouncing. Here is an example:
var timeout, delay = 3000;
function func1() {
clearTimeout(timeout);
timeout = setTimeout(function(){
alert("3000 ms inactivity");
}, delay);
}
<input type="text" oninput="func1()">
When emitted, simply call func1(), and if there are no new emissions after 3000 ms, the function in timeout will be executed.
It would be better to understand the problem and use case if you add the code also.
but As I could understand the problem these is two way
if you using inside input and triggering based #changed event you can add #change.lazy this not trigger on each change.
second solution is to use setTimeout(function,delayInMs) inside parent
vuejs Docs link
By simply changing the handleFilters function to :
handleFilters(filters) {
clearTimeout(this.timeout);
this.timeout = setTimeout(
this.getGamerTags,
this.delay,
filters.find(o => o.field == "playerGamerTag").filter
);
},
the problem is solved.
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--;
}
}
I have currently assigned a setTimeout function to a Vue method and I want to use clearTimeout for this function. Is that possible? If so, how can I do this?
methods: {
timeoutController() {
setTimeout(function() {
this.controllerShown = false;
}, 3000);
}
....
new Vue({
el: '#app',
data: {
timer: null
},
methods: {
startTimer () {
this.timer = setTimeout(() => {
console.log("execute me")
}, 3000)
},
// If you kill the timer before setTimeout callback has been executed the callback wont get executed
killTimer () {
if (this.timer) {
clearTimeout(this.timer)
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="startTimer()">start</button>
<button #click="killTimer()">kill</button>
</div>
Why do you need to assign a function to a Vue component's data?
setTimeout() will execute the function 3000 ms later and give the timer to this.timeout. The timer will never execute again, no matter where you assign to.
I made a codepen of my issue here https://codepen.io/stevemr/pen/VNQbYe
I have a root Vue instance which maintains the props for a component, VideoPlayer. My root instance has a method called setVideo, which is just assigning some dummy values right now.
Here's the object I'm using in the data of the root instance:
video: {
drive: '',
filename: '',
mediaType: '',
},
Here's the setVideo function:
setVideo: function() {
// Get the drive, filename, and mediaType
this.video.drive = 'hdd1';
this.video.filename = 'game-of-thrones_s01e04.mp4';
this.video.mediaType = 'show';
// Hide all modals and trigger the display of the video player
Event.trigger('hideModal');
Event.trigger('displayVideoPlayer');
},
The Event class is just a wrapper for basic Vue events:
window.Event = new class {
constructor() {
this.vue = new Vue();
}
trigger(event, data = null) {
this.vue.$emit(event, data);
}
listen(event, callback) {
this.vue.$on(event, callback);
}
};
Here's the DOM where my VideoPlayer component is initialized:
<video-player
v-bind:drive="video.drive"
v-bind:filename="video.filename"
v-bind:media-type="video.mediaType"
></video-player>
And finally, here's my VideoPlayer component:
<template>
<div>
<div id="movie-container">
<div
class="video-loader top-most"
v-if="showVideoPlayer && !loaded"
></div>
<video
id="video-player"
ref="video"
v-if="showVideoPlayer && src !== ''"
class="top-most"
v-bind:class="{ hidden: !loaded }"
v-on:click="togglePlay"
controls
autoplay
>
<source v-bind:src="src" v-bind:type="videoType"></source>
</video>
</div>
<div id="time-range-container" v-if="showTimeRange">
<input
id="time-range"
ref="timeRange"
type="range"
min="0"
v-bind:max="duration"
step="30"
v-model:value="currentTime"
/>
</div>
</div>
</template>
<script>
export default {
props: [
'drive',
'filename',
'mediaType',
],
data() {
return {
currentTime: 0,
duration: 0,
loaded: false,
showTimeRange: false,
showVideoPlayer: false,
}
},
computed: {
src: function() {
if(this.filename !== '') {
return
'/video/' + this.drive +
'/' + this.mediaType +
's/' + this.filename;
}
return '';
},
videoType: function() {
var ext = this.filename.split('.')[1];
var type = '';
switch(ext) {
case 'mk4':
case 'm4v':
type = 'webm';
break;
case 'avi':
type = 'ogg';
break;
default:
type = ext;
}
return 'video/' + type;
},
},
created() {
Event.listen('displayVideoPlayer', this.display);
},
methods: {
display: function() {
if(this.src === '') {
return;
}
this.showVideoPlayer = true;
this.loaded = false;
var self = this;
setTimeout(function() {
var interval = setInterval(function() {
var video = self.$refs.video;
if(video.readyState > 0) {
self.loaded = true;
self.duration = Math.round(video.duration);
self.currentTime = video.currentTime;
clearInterval(interval);
}
}, 500);
}, 800);
},
togglePlay: function() {
var video = this.$refs.video;
if(video.paused) {
video.play();
}
if(!video.paused) {
video.pause();
}
},
},
}
</script>
When setVideo is called it should set the VideoPlayer component's props to the dummy values and then the video player should be displayed. But instead when the displayVideoPlayer event is fired, the component props are still set to their default values (empty strings). Most importantly, the src computed property is not being updated before the display method is called, so the display function immediately returns without doing anything.
It's like my component's props and data aren't being updated, even though I can see with the dev tools that they are. It's like it's just not happening fast enough or something.
I've tried making src part of the component's data and setting it in the display function with another function, setSrc. But the same thing happened.
I've also tried moving Event.listen('displayVideoPlayer', this.display); into mounted() instead of created(), also did not fix anything.
If you look at the codepen, the first time you click the button to trigger the setVideo function, the video player component should be displayed, instead it takes 2 clicks.
It seems the problem is a race condition between Vue updates the value and You call display method:
display: function() {
console.log(this.src) // ""
setTimeout(() => console.log(this.src)) // "/video/hdd1/shows/game-of-thrones_s01e04.mp4"
if(this.src === '') {
return
}
This mean you call display method before the value is update.
One way to solve you is add some delay before your call display method:
setVideo: function() {
this.video.drive = 'hdd1'
this.video.filename = 'game-of-thrones_s01e04.mp4'
this.video.mediaType = 'show'
setTimeout(() => {
Event.trigger('displayVideoPlayer')
})
But I think this might get more problems in the future. If you want to rely on props then you should use watcher pattern instead:
watch: {
src (src) {
if(src === '') {
return
}
// ... display
}
}
Or pass those values through your event not on props like:
Event.trigger('displayVideoPlayer', this.video)