_.debounce and Iron.controller() in eventmap - javascript

I want to implement a debounced search with iron-router where the search query is written into the controllers state. unfortunately the only way I could get _.debounce to work is to pass it directly to the event map like so:
Template.search.events({
'keydown #search': _.debounce(function(event) {
var controller = Iron.controller();
}, 750)
});
unfortunately Iron.controller() doesn't know the context here so an error drops.
But if I nest the debounce inside a function to get the Iron.controller(), _.debounce never fires.
Template.search.events({
'keydown #search': function(event) {
var state = Iron.controller().state;
var q = $(event.currentTarget).val();
_.debounce(function() {
state.set("q", q);
}, 750);
}
});
Has anybody done something similar and a solution to this problem?

I'm not familiar with debounce, but if I'm not mistaking, the following should work:
var doIt = _.debounce(function(func){
func()
}, 750);
Template.search.events({
'keydown #search': function(event) {
var state = Iron.controller().state;
var q = $(event.currentTarget).val();
doIt(function(){
state.set("q", q);
})
}
});

I have found that iron-router uses a fair amount of bare this throughout the code.
Perhaps the scope is not correctly set when you debounce the event handler. If this is the case, you could try binding it to this:
Template.search.events({
'keydown #search': _.debounce(function(event) {
var controller = Iron.controller();
}.bind(this), 750)
});
Maybe it should be bond to events. I'm not sure, as I'm not familiar with this library.
As for the second example, as Peppe L-G correctly suggests it, debounce works by returning a debounced function. In your example, you never call it!
a = function(){};
b = _.debounce(a, 200);
a() // immediate
b() // waits 200 millis, calls a()
Also, it's important to notice that you should not declare the debounced function inside the event handler. Otherwise, you'll always create a new debounced function and all of them will fire at the first opportunity, defeating the purpose.
I recommend you name your debounced handler more appropriately. For example:
var handleKeydownEventDebounced = _.debounce(function(controller, query){
controller.state.set("q", query);
}, 750);
Template.search.events({
'keydown #search': function(event) {
handleKeydownEventDebounced(Iron.controller(), $(event.currentTarget).val());
}
});

Related

Javascript Debounce not clearing it's queue from vue component

I am using the debounce method from here https://www.freecodecamp.org/news/javascript-debounce-example/
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function saveInput(){
console.log('Saving data');
}
const processChange = debounce(() => saveInput());
and I want to include in a library we have, so in common.js I have:
export default {
otherStuff,
debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
},
in vue.js I have a textbox which has an event #keyup="searchboxChange()"
and in the methods section:
import common from "#/assets/js/Service/Common";
... stuff removed for brevity
methods:
{
searchboxChange(){
common.debounce(() => this.filterChanged(), 1000)();
},
}
I had to include () at the end of the debounce method else it didn't actually fire. However, while it debounces perfectly, when the timeout expires every event is then fired. So if my search was "HELLO" I would see 5 requests all fired at the same time as this.filterChanged() was called 5 times.
I am sure it is something simple with the scope of the timeout variable, because adding a console.log into the debounce method shows the timer is undefined each time.
You need to debounce the component method, otherwise you'll be invoking multiple debounced functions from within your component method.
Something like this should work
methods: {
// ...
searchboxChange: common.debounce(function() {
this.filterChanged();
}, 1000)
}
Notice the use of function as opposed to short function syntax. You'll need to do this to ensure the correct lexical scope of this
Firstly, as always, thanks to everyone who contributed a solution. However, none got past the "this" is not the right scope.
The solution was to set the function in created. (source: https://forum.vuejs.org/t/lodash-debounce-not-working-when-placed-inside-a-method/86334/4)
which (in case link goes dead) is effectively saying
move the part that you need to debounce into its own method and debounce that (like you did in the codepen for he first method).
Then you can call the debounced method from the event handler method.
It’s also better to debounce each instance method dynamically during created, otherwise component instances that the same debounced function and that can lead to wonky debounce behaviour:
and their code sample:
created() {
this.updateForm = _.debounce(this.updateForm, 500)
},
methods: {
triggerUpdate(event){
// perform some action before debouncing
this.updateForm(event)
} ,
updateForm: (event){
console.log('in update')
}
so for my case:
created() {
this.searchboxChange = common.debounce(this.filterChanged, 1000)
},
yes, that is literally it.
result:
only one network call now.

How do you pass event to a setTimeout

I currently have:
var content = document.querySelectorAll(".content");
var myButtons = document.querySelectorAll(".more-button");
function enableContent(evt){
// remove class d-none so it can appear
content[evt.currentTarget.param].className = content[evt.currentTarget.param].className.replace(/\bd-none\b/g, "");
}
for(var i=0; i<myButtons.length; i++){
myButtons[i].addEventListener("click", enableContent);
myButtons[i].param = i;
}
Each myButton has it's own content. I'm trying to make content appear slowly when myButton is clicked like so:
for(var i=0; i<myButtons.length; i++){
myButtons[i].addEventListener("click", function(){setTimeout(enableContent, 2000, evt)}));
myButtons[i].param = i;
}
But it doesn't work, there is a problem with the parameter evt. How do I pass in evt to setTimeout?
I would suggest making solution easier by setting the setTimeout inside the enableConent directly
function enableContent(evt){
// remove class d-none so it can appear
setTimeout( () => {
content[evt.currentTarget.param].className =
content[evt.currentTarget.param].className.replace(/\bd-none\b/g, "");
}, 2000);
}
But if you need to learn why the event is not being passed to the enableContent event handler we need to know how setTimeout and function binding works.
setTimeout take a callback (fancy word for passing a function to another function) and a delay time(that guarantees at least x time before calling the callback function.
so using setTimeout should be used like
setTimeout( enableContent, 2000 );
yet this code won't pass the event so we need to bind the function enableContent with the event object so when the event handler is called the function know and access the event object properly
myButtons[i].addEventListener("click", function(event){
setTimeout( enableContent.bind(event), 2000);
});
binding in js has many types, implicit, explicit, hard, & new binding. using bind function bind the enalbeConent with the event and return a new function that is hard bound with the event object.
You need to pass the event (evt) to the function itself, and I think maybe you are moving the wrong way using the setTimeout, maybe you don't need to use a setTimeout? You can use css animation or transition with *-delay properties?
But if you are using setTimeout, you need to clear timeout after every click using the clearTimeout method:
myButtons[i].addEventListener("click", function(evt) {
if(evt.currentTarget.timer) {
clearTimeout(e.currentTarget.timer);
}
evt.currentTarget.timer = setTimeout(enableContent, 2000);
}));
Example: https://jsfiddle.net/6kcq2ma8/
Easy peasy!
window.addEventListener("load", (event)=> {
let setTimeoutHandler = (event) =>{
console.log(event);
}
setTimeout(()=>{
setTimeoutHandler(event);
}, 1000);
});

Javascript : setTimeout use on an anonymous function expression

I recently started learning javascript to help maintain some stuff and ran into this issue today:
this.moveChar = function(){
// body here
setTimeout(moveChar,1000);
}
this.initialise= function(){
this.moveChar();
}
When initialise is called, I expected moveChar to be called, then repeated call itself once every 1000ms
However, what actually happens is moveChar gets called once then that's it. Based on other stackoverflow posts I read, I suspected it might be something to do with the function being expressed rather than declared. I have tried to use
this.moveChar = function recMove(){
// body here
setTimeout(recMove,1000);
}
without luck either.
Any suggestions on how I can fix this?
EDIT: Main thing I need to do is have the moveChar function called once every second. If there is a better approach than setTimeout recursion, I'm open to it
this.moveChar is not the same as moveChar, unless this is the global scope object like window.
this.moveChar is a property on an object, while moveChar would reference any variable in a visible scope chain.
You can change it to a couple of things in order to keep scope of whatever object is being used:
Using an arrow function
this.moveChar = function(){
// body here
setTimeout(()=>this.moveChar(),1000);
}
Using .bind()
this.moveChar = function(){
// body here
setTimeout(this.moveChar.bind(this),1000);
}
You might want to consider using setInterval() which is the more appropriate API for this task.
What setInterval() does is - it will repeatedly call the given function upon a certain interval is reached.
See:
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval
Quote:
Repeatedly calls a function or executes a code snippet, with a fixed
time delay between each call. Returns an intervalID.
Example:
Assuming moveChar() contains your operation logic. Then to repeat it you'll do this 1 line.
let moveChar = function(){
// Do stuff
console.log("Hi thanks for calling me!");
}
setInterval(moveChar, 1000);
Are you using this in side body here?
If so, you should bind correct context while call.
this.moveChar = function(){
// body here
setTimeout(this.moveChar.bind(this), 1000);
}
Or use anonymous function:
this.moveChar = function(){
// body here
var that = this;
setTimeout(function(){
that.moveChar();
}, 1000);
}
Or arrow function:
this.moveChar = function(){
// body here
setTimeout(() => this.moveChar(), 1000);
}
Same notes apply to setInterval variant:
this.initialise= function(){
setInterval(this.moveChar.bind(this), 1000);
// var that = this;
// setInterval(function(){that.moveChar();}, 1000);
// setInterval(() => this.moveChar(), 1000);
}
this.moveChar = function(){
// body here
alert('called moveChar');
}
this.initialise= function(){
setInterval(function(){moveChar();},1000);
}
this.initialise();//call here

How to pass arguments to a function in setTimeout

I have the following code:
function fn($){
return function(){
innerFn = function(){
setTimeout(show, 1000);
};
show = function(){
$.alert("TEST");
}
}
}
But, after one second, when the function show is run, it says $ is undefined. How do I resolve this issue?
how to pass arguments to a function in setTimeout
setTimeout has a built in mechanism for adding params
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
use it.
If you're going to use this - you should be careful. but that's another question.
There are a number of things at play here. The most important being that your setTimeout never gets called, since innerFn never gets called. This should do the trick.
function fn($){
return function(){
setTimeout(function(){
$.alert("TEST");
}, 1000);
}
}
fn(window)(); //triggers your alert after 1000ms
Your code makes no any sense, because nothing is called:
function fn($){
return function(){
innerFn = function(){
setTimeout(show, 1000);
};
show = function(){
$.alert("TEST");
}
}
}
Let's say I'm calling fn passing window, then a function is returned, that I can executed. But because this function is containing only function declaration - you also forget var so you pollute the global scope, that is bad - nothing is happen.
You'll need at least one function call inside, like:
function fn($){
return function(){
var innerFn = function(){
setTimeout(show, 1000);
};
var show = function(){
$.alert("TEST");
}
innerFn();
}
}
fn(window)();
And that will works. However, it's definitely redundant. You can just have:
function fn($){
return function(){
function show(){
$.alert("TEST");
}
setTimeout(show, 1000);
}
}
To obtain the same result. However, if you're goal is just bound an argument to setTimeout, you can use bind. You could use the 3rd parameter of setTimeout as the documentation says, but it seems not supported in IE for legacy reason.
So, an example with bind will looks like:
function show() {
this.alert('test');
}
setTimeout(show.bind(window), 1000);
Notice also that window is the global object by default, so usually you do not have to do that, just alert is enough. However, I suppose this is not your actual code, but just a mere test, as the alert's string says.
If you prefer having window as first parameter instead, and you're not interested in the context object this, you can do something like:
function show($) {
$.alert('test');
}
setTimeout(show.bind(null, window), 1000);

Event never firing?

I am trying to add some event listener to document, but for some reason it looks like the click event is never fired, because the callback is never called:
function NotJquery(element) {
this.element = element;
return this;
}
NotJquery.prototype.eventFired = function(event, callback) {
console.log('in NotJquery prototype');
return function (event, callback) {
element.addEventListener(event, callback, true);
};
};
var $ = function(element) {
return new NotJquery(element);
};
function Test() {
}
Test.prototype.addListener = function() {
console.log('in Test prototype');
$(document).eventFired('click', function() {
console.log('click event fired');
});
};
(function() {
var test= new Test();
test.addListener();
}());
Both the messages: "in Test prototype" and "in NotJquery prototype" are logged in the console, but when I click somewhere in my document the message "click event fired" is not output in the console. I do not see what is wrong with my code. Anybody has an idea to get it working?
http://jsfiddle.net/Nn3tZ/1/
Your client code is expecting something like this:
NotJquery.prototype.eventFired = function(event, callback) {
this.element.addEventListener(event, callback, false);
};
Working demo: http://jsfiddle.net/Nn3tZ/2/
element is not defined within your eventFired function (but that's not the only problem). Here's a minimal update:
NotJquery.prototype.eventFired = function(event, callback) {
var self = this; // <== Change 1 of 2
console.log('in NotJquery prototype');
return function () {
self.element.addEventListener(event, callback, true);
// ^-- Change 2 of 2
};
};
Unlike some other languages (Java, C#), this is not optional when referring to a property on the current object. Separately, the function you're creating within eventFired won't have the right this, so that's why I've stowed it away as self and then used self within the generated function (which is a closure).
Separately, you're passing event and callback into eventFired but then also declaring them on the generated function (it's not at all clear to me why you're generating a function there at all), so the ones you pass into eventFired are never used.
More reading (on my anemic blog):
Mythical Methods
You must remember this
Closures are not complicated

Categories

Resources