How to use debounce on `keyup` with Vue.js - javascript

I am trying to catch the user started typing, and stopped typing using the debounce function. I tried Lodash and Underscore.js.
On my textArea
v-on:keyup="handler($event)"
handler: function(e) {
this.e = e
if(this.canPublish) {
this.setCanNotPublish()
this.doStuff()
}
var debounceFunction = _.debounce(this.doneTyping(), 5000)
debounceFunction(e)
},
I am getting really frustrated about this. In pure JavaScript I made the test work. But with Vue.js where it uses v-on events, data, methods ...etc., I am not able to make it work.
Method doneTyping
doneTyping: function () {
console.log('done typing....')
}
}
Method doStuff
doStuff: function () {
console.log('started typing....')
}
The intended behaviour is: first the user started typing in the textArea, and it starts the doStuff. And if the user keeps typing in a period shorter than 5 seconds, it will not trigger doStuff again because canPublish is a boolean. Next the user stops typing and then the debounce func is done, and doneTyping fires.

I would do with this two debounced functions, one for started typing which triggers on the leading edge, and one for stopped typing which triggers on the trailing edge.
new Vue({
el: '#app',
created() {
this.startedTyping = _.debounce(this.startedTyping, 5000, {
leading: true,
trailing: false,
})
this.stoppedTyping = _.debounce(this.stoppedTyping, 5000, {
leading: false,
trailing: true,
})
},
methods: {
handleKeydown() {
// This triggers on the leading edge
this.startedTyping()
// This triggers on the trailing edge (after 5s)
this.stoppedTyping()
},
startedTyping() {
console.log('started typing')
},
stoppedTyping() {
console.log('stopped typing')
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.min.js"></script>
<div id="app">
<textarea #keydown="handleKeydown"></textarea>
</div>
Your code is wrong because you are creating a new debounced function every time the handler is called. You need to have only one debounced function instance that you call each time. It's best to create the debounced function in the created hook.
I often see code like this:
methods: {
myDebouncedFunc: _.debounce(function () {
// Do stuff
}, 1000)
}
This is technically not wrong, but you may not realize that the debounced function will be shared across all instances of that component, which may not be what you want. It's usually best to create the debounced function in the created hook so that each instance of the component gets its own independent debouncing tracking.

Here's how to debounce your method.
doneTyping: _.debounce(function (e) {
console.log('done typing....')
}, 5000)
Then you use it like
handler: function(e) {
this.e = e
if(this.canPublish){
this.setCanNotPublish()
this.doStuff()
}
this.doneTyping() // function is debounced
},

Related

How to implement a throttle method in Vuejs?

Currently I'm working inside a chat application using webSockets. I've implemented a indicator which shows that one of the chat members is currently typing. I have another call which fires 5 seconds after the person starts typing to remove the indicator. The problem I'm having is that if the person continues to type longer than 5 seconds then the 'typing' indicator flashes in and out rapidly in the UI... Here is my current implementation.
sendChatState({ commit, dispatch, state }, payload) {
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'composing'})
// Create set timeout here to pause typing indicator...
setTimeout(() => {
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'pause'})
}, 5000)
},
Appears I may need to use some type of throttle to limit the amount of calls. However this is where I'm having trouble as I'm not entirely sure how to implement this.
Let's set the flag typing to FALSE at the beginning. On each change of the input value (meaning there is typing) you start a timer (and first cancel the previous timer, if there was an active one) and if the flag typing was FALSE - set it to TRUE and show the indicator (otherwise it has already been shown). Once the timer fires - you hide the indicator and set the flag typing to FALSE.
<template>
<textarea v-model="message" #input="setFlag"/>
</template>
<script>
...
data()
{
return {
message: '',
typing: false,
timer: null,
}
},
beforeDestroy()
{
if (this.timer) clearTimeout(this.timer);
},
methods:
{
setFlag()
{
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.timeOut, 5000);
if (!this.typing)
{
this.typing = true;
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'composing'});
}
},
timeOut()
{
this.typing = false;
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'pause'});
this.timer = null;
}
}
</script>
You can solve it using debounce from lodash. Doing:
_.debounce(callServiceMethod, delay_time, { 'leading': true })
The debounce function is sending you back a throttled version of the function when you are calling it. In order to access it from the component scope and in the template within an eventHandler, the easiest way would be something like this:
import debounce from 'lodash/debounce'
export default {
...,
methods: {
sendChatState: debounce(function() {
do your thing here
}, delay_time, { 'leading': true }),
},
...
}
Note you need to use the config parameter to immediate call the method.
More info here.

Getting data from a watched variable

I have a beginner question. Maybe I´m just too stupid.
Situation:
I have a interface where I can set alarm timer values via a slider. Everytime the value gets updated, it is written down to a file.
But when I set an interval I dont have access from the callback function to the watched variable, see timeCallback().
Vue.component('alarm-comp', {
data: function() {
return {
data: {
wakeup: {
text: "Get up",
time: 30, //timeout
},
...
},
test: "test",
...
},
watch: {
data: {
handler(val) {
this.saveSettings();
},
deep: true
}
},
mounted: function() {
this.readSettings();
setInterval(() => {this.timerCallback()}, (this.data.wakeup.time) * 1000); // -> time correctly set
},
methods: {
timerCallback() {
console.log(this.test); // -> test
console.log(this.data.wakeup.time); // -> undefined
},
}
Hi mago and welcome to SO.
In VueJS information is sent down to components with props and sent up to the parent with emit event. If you want the parent component to know when the interval is done, you should emit that event up.
Check out this example: https://codepen.io/bergur/pen/jjwZxO?editors=1010
In my alarm component the main thing is
mounted() {
setInterval(() => {
this.$emit('finished', this.wakeup.text)
}, (this.wakeup.time) * 1000
);
},
Here I create the finished event that sends the wakeup text (note that I removed the extra data wrapper)
In my parent object I listen to that event
<Alarm #finished="logMessage" />
And all that logMessage does is this:
methods: {
logMessage(msg) {
console.log('the alarm has finished and sent this message: ', msg)
}
},
Note that setInterval emits the event multiple times.
i'm trying to figure it out since yesterday, for some reason, your code is not executing the mounted hook, i just duplicate your code (is missing a close bracket before watch, maybe just for the example code) and realise that if you change the mounted for other hook like created, this i'll trigger and i'll work, at least in my example.
created() {
this.readSettings();
setInterval(() => {this.timerCallback()}, (this.data.wakeup.time) * 1000);
}
Do you really need the mounted hook, or can you change to created?

Vue - check if user is offline and then show div for a second once they come back online

I currently am using a function on a timed loop to check if a user goes offline:
created() {
setInterval(() => {
this.checkOnline();
}, 30000);
}
Method:
checkOnline(){
this.onLine = navigator.onLine ? true : false;
}
Is there a way I can detect this without using the timer?
Secondly...
I am trying to show an 1 second alert to tell the user they are back online. When this.onLine is true, the div should be hidden. When it is false it should also be hidden, but when it goes from false to true I want to show the div for a second or so and then hide it again. I have tried this with a settimeout and using a watcher but neither give the desired effect.
Edit:
So one method that gets me there is:
data() {
return {
onLine: navigator.onLine ? true : false,
}
}
then a watcher to show the back online message
watch: {
onLine: function (val) {
if(this.onLine === true){
this.showBackOnline = true;
setTimeout(()=>{ this.showBackOnline = false; }, 1000);
}
},
},
Is there a better way than a watcher to achieve this, other than using a dedicated notify plugin?
Is there a way I can detect this without using the timer? Yes. By using the online offline event.
Is there a better way than a watcher to achieve this, other than using a dedicated notify plugin? I think the watcher is the most suitable method here.
demo: https://jsfiddle.net/jacobgoh101/xz6e3705/4/
new Vue({
el: "#app",
data: {
onLine: navigator.onLine,
showBackOnline: false
},
methods: {
updateOnlineStatus(e) {
const {
type
} = e;
this.onLine = type === 'online';
}
},
watch: {
onLine(v) {
if (v) {
this.showBackOnline = true;
setTimeout(() => {
this.showBackOnline = false;
}, 1000);
}
}
},
mounted() {
window.addEventListener('online', this.updateOnlineStatus);
window.addEventListener('offline', this.updateOnlineStatus);
},
beforeDestroy() {
window.removeEventListener('online', this.updateOnlineStatus);
window.removeEventListener('offline', this.updateOnlineStatus);
}
})
you can handle this (update onLine status), just by set arrow function in event listener and set that varible true/false by listener.
window object has online/offline events and you don't need to watch for navigator.online value or set interval to check it. you can just set event listener for it and do something on callback method.
in below code i just change value of my 'isOnLine' variable value and in template show message by v-if directive to user about online/offline.
data() {
return {
isOnLine:navigator.onLine
}
},
mounted() {
window.addEventListener('online', ()=>{this.isOnLine=true});
window.addEventListener('offline', ()=>{this.isOnLine=false});
},

Running setInterval on Vue method

document.addEventListener("DOMContentLoaded", function(event) {
var app = new Vue({
el: '#app',
data: {
posts: {}
},
mounted: function() {
},
methods: {
updatePosts: function () {
$.get('/api/v1/posts',function(response){
this.posts = response.data;
setInterval(this.updatePosts, 10000);
}.bind(this));
};
},
created: function () {
this.updatePosts();
}
});
});
I'm making an api call and want to run the updatepost method every 10 seconds. Currently, it runs except the 10 second timer gets reset on every page reload. How would I run this function every 10 seconds but not when the page gets refreshed?
I'm not sure if I understand your problem correctly, you want to stop the updatePosts call if the page refreshes, maybe you could use a route param to allow the created event to trigger updatePosts only if said param exists.

Passing method to lodash with vue gives 'Expected a function'

I am trying to pass a Vue function to the lodash throttle method. Shouldn't I just be able to do something like this?
When I am trying to do this I am getting the following error:
Error in callback for watcher "query": "TypeError: Expected a function"
Watcher
watch: {
query() {
throttle(this.autocomplete(), 400);
}
}
Methods
methods: {
autocomplete() {}
}
Even though I am passing a function reference I am still getting an error message. If I wrap it with a anonymous function it won't fire:
throttle(() => { this.autocomplete(); }, 400);
I just checked and the autocomplete function does actually seem to fire regardless of the error that it is not a function in my example at the top.
What is going wrong here?
Edit:
Jsfiddle: http://jsfiddle.net/yMv7y/2780/
You are passing the return value of this.autocomplete() (maybe undefined) and not the function reference. If you want to do the latter, you have to omit the brackets:
watch: {
query() {
throttle(this.autocomplete, 400);
}
}
Working approach:
var demo = new Vue({
el: '#demo',
data: {
query: ''
},
watch: {
query: function() {
this.autocomplete()
}
},
methods: {
autocomplete: _.throttle(function() {
console.log('test');
}, 50)
}
})
<script src="http://vuejs.org/js/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<div id="demo" v-cloak>
<input type="text" v-model="query">
</div>
As #Bill Criswell commented,
This is creating a throttled function every time query changes. You
want to constantly call the same throttled function like the answer
below.
My guess is that you need to define the throttled function with a non-invoked callback in a variable, and then invoke that as a function:
var throttled = throttle(this.autocomplete, 400)
watch: {
query() {
throttled();
}
}
Just spent quite awhile trying to figure that one out...

Categories

Resources