I have a member function in the object which gets the array of callback functions and the name of the event for which this function is set:
...
setHandlesByList: function (list) {
for (var i in list) {
var self = this;
$(document).on(list[i].name, function (e) {
list[i].callBack.call(self,e)
});
};
},
...
Somewhere in the child objects I have a call to this function of the parent object:
...
initClass: function () {
this.setHandlesByList([
{ name: 'configChecked', callBack: onConfigChecked },
{ name: 'configExpired', callBack: onConfigExpired },
]);
},
onConfigChecked: function() {
// some code
},
onConfigExpired: function() {
// some code
},
....
but something goes wrong - for all events the handler is the last set callback function...
Try the following:
setHandlesByList: function (list) {
for ( var i = 0; i < list.length; i++ ) {
addCallback(list[i].name, list[i].callback);
}
function addCallback(on, name, callback) {
$(document).on(name, function(e) { callback.call(on, e); });
}
},
There is a problem with your scoping, because the value of i eventually ends up being the last value of i when your callbacks are evaluated.
Also note that you could use list.forEach.
Each event handler function you create in this code:
setHandlesByList: function (list) {
for (var i in list) {
var self = this;
$(document).on(list[i].name, function (e) {
list[i].callBack.call(self,e)
});
};
},
...has an enduring reference to list and i, not copies of them as of when the function is created. Since i ends up being the last property enumerated, all handlers end up referring to the same list entry.
Instead, create a builder function to create the callback (sorry, I can't recreate the indentation style you use, I've just used a fairly standard one):
setHandlesByList: function (list) {
var self = this;
for (var i in list) {
$(document).on(list[i].name, buildHandler(list[i]));
};
function buildHandler(entry) {
return function (e) {
entry.callBack.call(self,e)
};
}
},
Now, the function created closes over entry, the argument to the buildHandler call, rather than over list and i. Since entry (the argument) doesn't change, the handler works.
Note also that I've moved the var self = this; out of the loop, as it didn't vary from iteration to iteration and so had no business being in the loop.
Side note: You've said that the function receives an array. If so, for-in (with no safeguards) is not the correct way to loop through the entries in that array. More: Myths and realities of for..in
Related
I am trying to edit every function under the UI "class" to return their first argument but still run their native code.
Here is what I have tried:
for(var i in UI) {
var cur = UI[i];
UI[i] = function() {
cur.call(this, arguments);
return arguments[0];
}
}
This returns my arguments but no longer runs the original function correctly as the UI element is not called.
Thanks in advance.
Typical JavaScript closure inside loops – simple practical example problem. There is only a single cur variable in your scope and it will have the value assigned to it in the last iteration of the loop.
Use let and pass the arguments along properly (via rest args and apply):
for(let i in UI) {
let cur = UI[i];
UI[i] = function(...args) {
cur.apply(this, args);
return args[0];
}
}
If you are stuck with ES5:
for(var i in UI) {
(function(cur) {
UI[i] = function() {
cur.apply(this, Array.prototype.slice.call(arguments));
return arguments[0];
}
}(UI[i]));
}
I have a js file with many functions
function one(){
//do stuff
}
function two(){
//do stuff
}
function execute_top_one(){
//do stuff
}
function execute_top_two(){
//do stuff
}
and I need to create a function that executes, for example, all functions that start with (or contain) "execute_top" in the function name, instead of having to call all the functions manually like this
execute_top_one();
execute_top_two();
Any suggestion?
I would suggest that you do it in a little other way:
const functionStore = {
one: function() {
console.log('one')
},
two: function() {
console.log('two')
},
execute_top_one: function() {
console.log('execute_top_one')
},
execute_top_two: function() {
console.log('execute_top_two')
},
}
const execute_these = "execute_top"
const executeFunctions = (functionStore, filterString) => {
Object.entries(functionStore).forEach(([fnName, fn]) => {
if (fnName.indexOf(filterString) !== -1) {
fn()
}
})
}
executeFunctions(functionStore, execute_these)
The difference is that you gather your functions into one object (I called it functionStore, and then create the filtering string & function. As you see from the snippet, filtering an object's keys and values (called fnName & fn in my snippet) is quite easy - and inside the filtering you can call the functions stored.
I have an array of objects that holds each "actionButton" id, selector and callback
var actionButtons = [
{
id:"0",
selector:"._55ln._qhr",
callback: undefined
},
{
id:"1",
selector:"._22aq._jhr",
callback: undefined
},
.
.
.
];
What I'm trying to do is calling a function with a specific parameter from the array(the id) every time a selector is clicked.
for(var i=0;i<actionButtons.length;i++){
$(document).on('click', actionButtons[i].selector, function() {
makeAction(actionButtons[i].id);
if (actionButtons[i].callback)
actionButtons[i].callback(this);
});
}
But this code is not working; it looks like every time the callback function is called the value of i is equal to the array size.
How can I solve this problem;ie. to make the value of the variable i become different for each callback.
The issue is because the i variable is being incremented in the loop. This means that when the first event handler actually runs after the loop completes, i is the maximum value, not 0.
To fix this you can use a closure:
for(var i = 0; i < actionButtons.length; i++) {
(function(i) {
$(document).on('click', actionButtons[i].selector, function() {
makeAction(actionButtons[i].id);
if (actionButtons[i].callback)
actionButtons[i].callback(this);
});
})(i);
}
In order not to fall into closure ambush you may always use array methods like
actionButtons.forEach(function(ab) {
$(document).on('click', ab.selector, function() {
makeAction(ab.id);
ab.callback && ab.callback(this);
});
});
You can use let:
The let statement declares a block scope local variable, optionally initializing it to a value.
Let will assure you the closure.
for(let i=0;i<actionButtons.length;i++){
$(document).on('click', actionButtons[i].selector, function() {
makeAction(actionButtons[i].id);
if (actionButtons[i].callback)
actionButtons[i].callback(this);
});
}
I have the following problem:
I'm trying to implement a Callback in JavaScript. Now I just made it with a global variable which holds my callbacl function. Here is the example:
_callbackFkt = null;
requestCompleted = function(oControlEvent) {
console.log("Callback: " + _callbackFkt.toString());
};
myLibRequest = function(callback) {
// some code, which is attached to the requestComplete event when ready
_callbackFkt = callback;
};
Now I try to call the functions which use the callback:
myLibRequest(function () {
// callback function 1
});
myLibRequest(function () {
// callback function 2
});
myLibRequest(function () {
// callback function 3
});
the result in the console is:
Callback: function () {
// callback function 3
}
How can I define the callback to be bound to one function call and not global available? I want the result:
Callback: function () {
// callback function 1
}
Callback: function () {
// callback function 2
}
Callback: function () {
// callback function 3
}
There are several ways to do what you are trying to do, but your basic problem is that you want a list of event handlers, but you are only assigning a single value.
To modify what you are currently doing:
_callbackFkts = [];
myLibRequest = function(callback) {
// some code, which is attached to the requestComplete event when ready
_callbackFkts.push(callback);
};
Then, when you want to execute the callbacks:
_callbackFkts.forEach(function(callbackFkt) {
callbackFkt();
});
But, this global mechanism is a bit messy. You might consider some encapsulation (untested, but you get the idea):
function Events() {
this.callbacks = [];
}
Events.protototype.bind = function(callback) {
this.callbacks.push(callback);
};
Events.prototype.executeAll = function(params) {
this.callbacks.forEach(function(callback) {
callback.apply(this, params);
}
}
Then you can use it like this:
var events = new Events();
events.bind(function() {
//callback function 1
});
events.bind(function() {
//callback function 2
});
events.bind(function() {
//callback function 3
});
events.executeAll('with', 'parameters');
Finally, you might just use an off-the-shelf event library. There are lots. One quick google search finds this.
Having a global as the callback will only work if myLibRequest() contains only synchronous code (which I assume it doesn't).
Remove the global, and use the callback that is passed in as an argument.
Assuming you have some async call in there, and you call requestCompleted when it's done. Add an argument so requestCompleted receives the callback, instead of referenceing the global.
requestCompleted = function(oControlEvent, callback) {
console.log("Callback: " + callback.toString());
};
myLibRequest = function(callback) {
myAsyncFunction(function(){
// async complete
requestCompleted('event', callback);
});
};
I have a requirement where I get the anchor tags id and based on the id I determine which function to execute.. so is there anything that suites below code
function treeItemClickHandler(id)
{
a=findDisplay(id);
a();
}
You can assign a function to a variable like so:
You can also return a function pointer from a function - see the return statement of findDisplay(id).
function treeItemClickHandler(id)
{
var a= findDisplay;
var other = a(id);
other();
}
function findDisplay(id)
{
return someOtherThing;
}
function someOtherThing()
{
}
Sure, functions are first class objects in JavaScript. For example, you can create a map (an object) which holds references to the functions you want to call:
var funcs = {
'id1': function(){...},
'id2': function(){...},
...
};
function treeItemClickHandler(id) {
if(id in funcs) {
funcs[id]();
}
}
As functions are treated as any other value, you can also return them from another function:
function findDisplay(id) {
// whatever logic here
var func = function() {};
return func;
}
functions are normal javascript values, so you can pass them around, (re)assign them to variables and use them as parameter values or return values for functions. Just use them ;) Your code is correct so far.
You can map between ids and functions to call in a number of ways.
One of the simpler ones is to create an object mapping ids to functions, and find the function to call from that object (this is in essence a nicer-looking switch statement).
Example:
function treeItemClickHandler(id)
{
var idMap = {
"some-id": findDisplay,
"another-id": doSomethingElse
};
if (!idMap.hasOwnProperty(id)) {
alert("Unknown id -- how to handle it?");
return;
}
// Call the corresponding function, passing the id
// This is necessary if multiple ids get handled by the same func
(idMap[id])(id);
}
function findDisplay(id)
{
// ...
}
function doSomethingElse(id)
{
// ...
}