Modifying number of arguments for callback functions - javascript - javascript

I know many of you already used JavaScript UI widget plugins, etc... that offers callback functions. For instance we have Object x and it has a function, let say .doThisAfterAnEvent(). And according to the official documentation of Object x, that function accepts a single parameter of type function() with one argument, let say _args.
To visualize, here is the example:
var handler = function(_args) {
// Do something.
}
var x = $("#element-to-widget-ify").transform()
x.doThisAfterAnEvent(handler)
My question is, how can I modify the method .doThisAfterAnEvent() to accept a function with two or more parameters instead of one? In this case, I need to pass a second extra value to the handler function.
Edit:
var widgets = {
"widget-at-the-nav": $("#nav-widget").transform(),
"widget-at-the-footer": $("#nav-footer").transform(),
"widget-at-the-search": $("#nav-search").transform(),
length: 3
}
var handler = function(_args, _option) {
console.log("key in: " + _option
// Other processes...
}
for(key in widgets) {
console.log("key outer: " + key)
widget[key].doThisAfterAnEvent(function(json) {
console.log("key out: " + key)
handler(json, key)
})
}
This is my attempt. But it prints like this:
key outer: widget-at-the-nav
key outer: widget-at-the-footer
key outer: widget-at-the-search
key out: widget-at-the-nav
key in: widget-at-the-nav
key out: widget-at-the-nav
key in: widget-at-the-nav
key out: widget-at-the-nav
key in: widget-at-the-nav
And I forgot to tell you guys that the function .doThisAfterAnEvent() (not the handler() function) has an AJAX call inside.

This question is a mess, so I'm only going to touch on the most recent edit, and the code it contains.
Your approach with masking the handler with an anonymous function is pretty much correct, it's just that your loop is messing up your lexical scope. The AJAX bit is a very important detail, because any AJAX calls are most likely going to operate long after you've looped, which means those callback functions will all reference the same final value of key.
You need to create a new scope where the key is 'locked in', so that the references are correct.
function Con () {}
Con.prototype.doThisAfterAnEvent = function (fn) {
// Fake some AJAX programming
window.setTimeout(function () {
fn.call(this);
}, 1500);
};
$.fn.transform = function () {
return new Con();
};
var widgets = {
"widget-at-the-nav": $("#nav-widget").transform(),
"widget-at-the-footer": $("#nav-footer").transform(),
"widget-at-the-search": $("#nav-search").transform()
};
var handler = function(_args, _option) {
console.log("key in: " + _option);
// Other processes...
};
for (var key in widgets) {
console.log("key outer: " + key);
(function (key) {
// Use an IIFE to establish a new lexical scope
widgets[key].doThisAfterAnEvent(function(json) {
console.log("key out: " + key);
handler(json, key);
});
}(key));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
In ES6, we'd use let.

If you ask that, I guess you mean at the moment you call doThisAfterAnEvent, you already know one parameter over two for your handler.
In this case, the solution is too wrap your handler with two parameter in a anonymous function that only take one parameter and then call back your handler :
x.doThisAfterAnEvent(function(_arg) { handler(myKnownArg, _arg) });

Related

A bug involving JavaScript's object structure

I'm writing a whole client-side system in JavaScript. I have about 4 .js files, but there is no need to post code from them, nor the html.
Anyway, before explaining the problem, I'll try to explain what is the code about, and the code itself.
Basically, it's a finite state machine in a design level. For now, I only have 3 states: the initial, a transition and the final state (the state machine, for now, is a queue). The code is this:
var state = 0
var IntInsert = //my object that represents a namespace, or a set of functions
{
insertCtrl : function() //main function: it calls the others
{
if (lookup()) return
if (!state) state = 1
var key = document.getElementById("newegg").value
switch(state)
{
case 1:
this.preInsert(key)
break
case 2:
this.firstInsert(key)
break
}
},
preInsert : function(key)
{
$("#terminal").css("color", "green")
$("#terminal").text("Inserting element '" + String(key) + "'")
$("#t2cell" + String(cuckoohash.h2(key))).css("background-color", "White")
$("button").prop("disabled", true)
$("input").prop("disabled", true)
$("#nextstep").prop("disabled", false)
state++
},
firstInsert : function(key)
{
key = [document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML, document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key][0] // key <-> t1[h1(key)]
if (!key)
{
$("#t1cell" + String(cuckoohash.h1(document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML))).css("background-color", "LightGreen")
this.finishedInsert()
}
},
finishedInsert : function()
{
$("#terminal").css("color", "green")
$("#terminal").text("Element '" + String(key) + "' inserted")
}
}
In the line this.firstInsert(key), a bug happens. Firebug tells me this: TypeError: this.firstInsert is not a function. This is weird, given that it's defined as a function and Firebug itself represents it as a function in it's high-level panel.
If I take the function out of the object and use it as a global function, there is no error at all. That's why I believe the whole semantics of the code can be ignored in order to answer my question, which is: why is this happening inside the object? What is it that I'm missing?
After reading the acknowledged answer, I realized it's important to state that the whole point of the code being in an object is to create a namespace for the whole state machine code I'm creating, so I definitely could put the state variable inside it. The idea is to avoid having large code base with global variables and functions.
Your issue is likely caused by how you are calling your methods. In Javascript, the value of this is determined by how a method is called, not by how it is declared. You don't show the calling code (where the problem likely is), but if you are calling one of your methods via some sort of callback, then you can easily lose the proper method call that will preserve the value of this for you. See this other answer for more info on how the value of this is determined based on how a function is called.
There are many different possible solutions.
Since your object is a singleton (there's only ever just one of them and you've given that one instance a name), I'd suggest that the simplest way to solve your problem is to just refer to the named singleton IntInsert rather than refer to this as that will solve any issues with the value of this in your methods.
You can do that like this:
var state = 0
var IntInsert = //my object that represents a namespace, or a set of functions
{
insertCtrl : function() //main function: it calls the others
{
if (lookup()) return
if (!state) state = 1
var key = document.getElementById("newegg").value
switch(state)
{
case 1:
IntInsert.preInsert(key)
break
case 2:
IntInsert.firstInsert(key)
break
}
},
preInsert : function(key)
{
$("#terminal").css("color", "green")
$("#terminal").text("Inserting element '" + String(key) + "'")
$("#t2cell" + String(cuckoohash.h2(key))).css("background-color", "White")
$("button").prop("disabled", true)
$("input").prop("disabled", true)
$("#nextstep").prop("disabled", false)
state++
},
firstInsert : function(key)
{
key = [document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML, document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key][0] // key <-> t1[h1(key)]
if (!key)
{
$("#t1cell" + String(cuckoohash.h1(document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML))).css("background-color", "LightGreen")
IntInsert.finishedInsert()
}
},
finishedInsert : function()
{
$("#terminal").css("color", "green")
$("#terminal").text("Element '" + String(key) + "' inserted")
}
}
Some Other Comments About the Code:
Leaving out semi-colons at the end of each statement may expose you to some accidental bugs. It seems a less than desirable practice to me. The first time you see such a bug, you probably be confused for quite awhile why your code isn't working as intended too.
You can chain in jQuery so instead of this:
$("#terminal").css("color", "green");
$("#terminal").text("Element '" + String(key) + "' inserted");
You can use this:
$("#terminal").css("color", "green").text("Element '" + String(key) + "' inserted");
In Javascript, you rarely need to explicitly cast to a string with String(key), so instead of this:
$("#terminal").text("Element '" + String(key) + "' inserted");
You can use this:
$("#terminal").text("Element '" + key + "' inserted");
Adding anything to a string will auto-convert that to a string for your automatically.
You went to the trouble to make a singleton namespace object IntInsert, yet you declare your variable state that is not in that namespace object. It seems you should do one of the other, not some odd combination of the two. You either want relevant things in your namespace object or not and such a generically named variable as state is ripe for a naming conflict. I would think it should be in your object.
It is odd to use a mixture of jQuery selectors like $("#terminal") and document.getElementById("newegg"). Code is usually cleaner if you stick with one model or the other. If you're using jQuery for other reasons, then code like:
var key = document.getElementById("newegg").value;
can be this in jQuery:
var key = $("#newegg").val();
In .firstInsert(), you declare an argument named key, but then in the first line of code in that method, you assign to key so the code is just misleading. You declare an argument, but are never using that argument if it is passed in. You should either use the argument or change the key variable to be just a local variable declaration, not a function argument.
In .finishedInsert(), you refer to a variable named key, but there is no such argument or variable by that name.
In .firstInsert(), this line of code is just bizarre:
var key = [document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML, document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key][0];
You appear to be declaring an array with two elements in it, then assigning just the first element to key. The second element in the array is an assignment to a .innerHTML property. This is simply a weird and misleading way to write this code. It should be broken up into multiple lines of code.
The first line of code in .firstInsert() appears to be referring to some sort of global definition of a variable named key which does not appear anywhere in your code. That is likely a bad idea. The key you use here should either be in your namespace object or should be passed as an argument to .firstInsert(xxx).
Here's a version of your code with as many of those items above fixed/modified and FIXME comments where things are still unexplained:
//my object that represents a namespace, or a set of functions
var IntInsert = {
//main function: it calls the others
state: 0,
insertCtrl: function () {
if (lookup()) return;
if (!IntInsert.state) IntInsert.state = 1;
var key = $("#newegg").val();
switch (IntInsert.state) {
case 1:
IntInsert.preInsert(key);
break;
case 2:
IntInsert.firstInsert(key);
break;
}
},
preInsert: function (key) {
$("#terminal").css("color", "green").text("Inserting element '" + key + "'");
$("#t2cell" + cuckoohash.h2(key)).css("background-color", "White");
$("button").prop("disabled", true);
$("input").prop("disabled", true);
$("#nextstep").prop("disabled", false);
IntInsert.state++;
},
firstInsert: function () {
// key <-> t1[h1(key)]
// FIXME here: There is no variable named key anywhere
document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key;
if (!key) {
$("#t1cell" + cuckoohash.h1(document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML)).css("background-color", "LightGreen");
IntInsert.finishedInsert();
}
},
finishedInsert: function () {
// FIXME here: There is no variable named key anywhere
$("#terminal").css("color", "green").text("Element '" + key + "' inserted");
}
};
I think you are having issue with what this is when you are calling your function. You have not shown how you are calling the function but most probably when you log this before that line, it won't be your IntInsert object but may be global Window object
The reason you are seeing the error is most likely because at runtime 'this' is changing to an object that doesn't have your function. Try restructuring your code and "closing" on this like this:
var IntInsert = function()
{
var self = this;
...
var insertCtrl = function() //main function: it calls the others
{
...
case 2:
self.firstInsert(key)
break
...
}
...
var firstInsert = function(key)
{
...
}
return {
insertCtrl: insertCtrl,
firstInsert: firstInsert
}
}
... or something similar that would fit your needs.

need help understanding closures usage in this code

Here is a simplified snippet from some code I wrote for managing tablet gestures on canvas elements
first a function that accepts an element and a dictionary of callbacks and register the events plus adding other features like 'hold' gestures:
function registerStageGestures(stage, callbacks, recieverArg) {
stage.inhold = false;
stage.timer = null;
var touchduration = 1000;
var reciever = recieverArg || window;
stage.onLongTouch = function(e) {
if (stage.timer) clearTimeout(stage.timer);
stage.inhold = true;
if (callbacks.touchholdstart) callbacks.touchholdstart.call(reciever, e);
};
stage.getContent().addEventListener('touchstart', function(e) {
e.preventDefault();
calcTouchEventData(e);
stage.timer = setTimeout(function() {
stage.onLongTouch(e);
}, touchduration);
if (callbacks.touchstart) callbacks.touchholdstart.call(reciever, e);
});
stage.getContent().addEventListener('touchmove', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdmove) callbacks.touchholdmove.call(reciever, e);
} else {
if (callbacks.touchmove) callbacks.touchmove.call(reciever, e);
}
});
stage.getContent().addEventListener('touchend', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdend) callbacks.touchholdend.call(reciever, e);
} else {
if (callbacks.touchend) callbacks.touchend.call(reciever, e);
}
stage.inhold = false;
});
}
later I call registerStageGestures on a few elements (represented by 'View' objects) in the same page. Something like:
function View() {
var self=this;
..
function InitView() {
...
registerStageGestures(kineticStage, {
touchstart: function(e) {
// do something
},
touchmove: function(e) {
// do something
},
touchendunction(e) {
// do something
},
touchholdstart: function(e) {
// do something
},
touchholdmove: function(e) {
// do something
},
touchholdend: function(e) {
// do something
},
}, self);
Everything works fine, however I'm left wondering about two things in the implementation of registerStageGestures:
First, is it necessary to make inhold, timer and onLongTouch members of the stage ? or will closures make everything works well if they are local vars in registerStageGestures ?
Second, is it necessary to call the callbacks with '.call(receiver,' syntax ? I'm doing this to make sure the callback code will run in the context of the View but I'm not sure if it's needed ?
any input is much appreciated
Thanks!
First, is it necessary to make inhold, timer and onLongTouch members
of the stage ? or will closures make everything works well if they are
local vars in registerStageGestures ?
As far as registerStageGestures() is concerned, var inhold, var timer and function onLongTouch(e) {...}. would suffice. The mechanism by which an inner function has automatic access to its outer function's members is known as "closure". You would only need to set stage.inhold, stage.timer and stage.onLongTouch if some other piece of code needs access to these settings as properties of stage.
Second, is it necessary to call the callbacks with '.call(receiver,'
syntax ? I'm doing this to make sure the callback code will run in the
context of the View but I'm not sure if it's needed ?
Possibly, depending on how those callbacks are written. .call() and .apply() are sometimes used when calling functions that use this internally. In both cases, the first parameter passed defines the object to be interpreted as this. Thus, javascript gives you the means of defining general purpose methods with no a priori assumption about the object to which those methods will apply when called. Similarly, you can call a method of an object in such a way that it acts on another object.
EDIT:
For completeness, please note that even in the absence of this in a function, .apply() can be very useful as it allows multiple parameters to be specified as elements of a single array, eg the ubiquitous jQuery.when.apply(null, arrayOfPromises)...
There are some simple answers, here.
First, closure:
Closure basically says that whatever is defined inside of a function, has access to the rest of that function's contents.
And all of those contents are guaranteed to stay alive (out of the trash), until there are no more objects left, which ere created inside.
A simple test:
var testClosure = function () {
var name = "Bob",
recallName = function () { return name; };
return { getName : recallName };
};
var test = testClosure();
console.log(test.getName()); // Bob
So anything that was created inside can be accessed by any function which was also created inside (or created inside of a function created in a function[, ...], inside).
var closure_2x = function () {
var name = "Bob",
innerScope = function () {
console.log(name);
return function () {
console.log("Still " + name);
}
};
return innerScope;
};
var inner_func = closure_2x();
var even_deeper = inner_func(); // "Bob"
even_deeper(); // "Still Bob"
This applies not only to variables/objects/functions created inside, but also to function arguments passed inside.
The arguments have no access to the inner-workings(unless passed to methods/callbacks), but the inner-workings will remember the arguments.
So as long as your functions are being created in the same scope as your values (or a child-scope), there's access.
.call is trickier.
You know what it does (replaces this inside of the function with the object you pass it)...
...but why and when, in this case are harder.
var Person = function (name, age) {
this.age = age;
this.getAge = function () {
return this.age;
};
};
var bob = new Person("Bob", 32);
This looks pretty normal.
Honestly, this could look a lot like Java or C# with a couple of tweaks.
bob.getAge(); // 32
Works like Java or C#, too.
doSomething.then(bob.getAge);
? Buh ?
We've now passed Bob's method into a function, as a function, all by itself.
var doug = { age : 28 };
doug.getAge = bob.getAge;
Now we've given doug a reference to directly use bobs methid -- not a copy, but a pointer to the actual method.
doug.getAge(); // 28
Well, that's odd.
What about what came out of passing it in as a callback?
var test = bob.getAge;
test(); // undefined
The reason for this, is, as you said, about context...
But the specific reason is because this inside of a function in JS isn't pre-compiled, or stored...
this is worked out on the fly, every time the function is called.
If you call
obj.method();
this === obj;
If you call
a.b.c.d();
this === a.b.c;
If you call
var test = bob.getAge;
test();
...?
this is equal to window.
In "strict mode" this doesn't happen (you get errors really quickly).
test.call(bob); //32
Balance restored!
Mostly...
There are still a few catches.
var outerScope = function () {
console.log(this.age);
var inner = function () {
console.log("Still " + this.age);
};
inner();
};
outerScope.call(bob);
// "32"
// "Still undefined"
This makes sense, when you think about it...
We know that if a function figures out this at the moment it's called -- scope has nothing to do with it...
...and we didn't add inner to an object...
this.inner = inner;
this.inner();
would have worked just fine (but now you just messed with an external object)...
So inner saw this as window.
The solution would either be to use .call, or .apply, or to use function-scoping and/or closure
var person = this,
inner = function () { console.log(person.age); };
The rabbit hole goes deeper, but my phone is dying...

how to add an argument to a method stored in an array that is called later

This is a follow-up to this question (although this is self-contained) trying to `call` three methods but not working correctly with jQuery map.
I am trying to store a set of methods in an array but there is a set that might have arguments like below (the initial methods are in before_methods and the proposed methods are in lm_methods). I'm sure it's pretty self explanatory what I want but I'd like to be able to merge in the arguments into a reasonable call to f (specifically the arc.pLikedByTerm). I currently have the following:
// signature
pLikedByTerm:function(term, ne, sw, m){
....
}
// code before_methods just to show
this.before_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems];
this.lm_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems, arc.pLikedByTerm('surfing'),arc.pLikedByTerm('sailing')];
$.each(this.lm_methods, function(i,f){
f(ne,sw,m);
});
How would I do this or is this bad design? What would be the idiomatic way? My brain is fried.
thx in advance
Update 1
Playing around with answer below, it looks like this works which might the simplest things:
var fns=[logStuff("this is msg"), logMoreArgs("a term","a you msg")];
for (var i=0; i<fns.length; i++) {
fns[i];
}
Having an array of functions is common practice when used often. For example, consider this Callback class.
function Callback(){
this.callbacks = [];
}
Callback.prototype.run = function(cb) {
for (var i=0; i<this.callbacks.length; i++) {
this.callbacks[i]();
}
};
We can then add some callbacks.
function logStuff(msg) {
jsprint(msg || "No message");
}
obj = new Callback();
obj.callbacks.push(logStuff);
obj.callbacks.push(logStuff);
obj.run();
If we run this we see that it's only logging our default value. So if we want to bind some data, we can use the bind function.
Function.prototype.bind
thisArg
The value to be passed as the this parameter to the target
function when the bound function is called. The value is ignored if
the bound function is constructed using the new operator.
arg1, arg2, ...
Arguments to prepend to arguments provided to the bound function
when invoking the target function.
Our new code sets the first parameter to different strings, which we then see. You can bind any number of parameters.
obj = new Callback();
obj.callbacks.push(logStuff.bind(null, "My message"));
obj.callbacks.push(logStuff.bind(null, "My other message"));
obj.run();
end result
The way you are doing would work just ok. Just remove the arguments and parens:
Instead of:
this.lm_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems,
arc.pLikedByTerm('surfing'),arc.pLikedByTerm('sailing')];
Do:
this.lm_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems,
arc.pLikedByTerm,arc.pLikedByTerm];
Example:
function say(txt) {
console.log("say" + txt);
}
function shout(txt) {
console.log("shout" + txt);
}
function whisper(txt) {
console.log("whisper" + txt);
}
var funcArr = [say, shout, whisper];
$.each(funcArr, function(i, f) {
f("hello");
});
would print:
sayhello
shouthello
whisperhello

create a javascript function programmatically

Need this for the youtube api // the onStateChange callback functions!
I want to programmatically create functions which will listen to the "onStateChange" event emitted by several youtube player. Adding the listener works already:
function onYouTubePlayerReady(playerId) {
var ytpStateManager = playerId +"_StateManager";
document.getElementById(playerId).addEventListener("onStateChange", ytpStateManager );
...
The function I need to create based on the playerId variable ("ytp_1", "ytp_2", ...) is
function ytpStateManager(newState) {
ytpStateHelper(playerId , newState);
}
So the result for the playerId "ytp_1" would look like this:
function ytp_1_StateManager(newState) {
ytpStateHelper("ytp_1", newState);
}
Works also but right now I need to add them manually for each player, which is not what I need. I want to create them automatically when a new player sends a readyState event.
My problem is that it seems like these functions need to be a global functions to work properly. I tried several options for days now. My problem is that I do not know how (if there is a way) to define a global function, incl. the function name, programmatically, based on another variable.
Its a bummer that the ytp does not emit an event which includes the state AND the player/target. Would make things much easier. All this is basically the workaround as I need all to do stuff on all stateChanges.
If there is a better/simpler way, PLEASE let me know :) Otherwise a solution for this question is highly welcome.
Maybe there is a way to rerout the event, to make it more "accessible"?
I read in the spec that .addEventListener also takes a object, so I tried to bind the event to a dedicated object. But again, it did not get triggered. Feels like I tested everything ...
UPDATE
I am now switching to the iframe player (from swfobject) because that one provides an event which includes playerId and state :D Yeahhh!! After spending week with the wrong ytplayer this feels like a great advancement. Also seems like yt wants us to use the iframe player which can dynamically use html5 when supported.
You create a function that returns a function:
function createStateManager(playerId) {
return function (newState) {
ytpStateHelper(playerId , newState);
}
}
Then you call your function factory when setting up the event listener:
var player = document.getElementById(playerId);
player.addEventListener("onStateChange", createStateManager(playerId));
DEBUGGING
I'm not sure why that's not working, but here is a debugging suggestion. I suspect you may not be getting the playerId on your onYouTubePlayerReady handler.
function onYouTubePlayerReady(playerId) {
console.log('Player ready. The player id is: ' + playerId);
var ytpStateManager = playerId +"_StateManager";
var player = document.getElementById(playerId);
player.addEventListener("onStateChange", createStateManager(playerId));
}
function createStateManager(playerId) {
return function (newState) {
console.log('State changed for player ' + playerId + '. New state is ' + newState);
ytpStateHelper(playerId , newState);
}
}
Could you try that, and post what you get from both console.log calls?
1)You can create Function object new Function([params], "BODY")
So you can combine body of your function as string variable and put into as BODY
Example:
var twoNumAverage = new Function("x", "y", "return (x + y)/2")
console.log(twoNumAverage(3,7))
2)And new can create dynamically name and BODY
Example
var globalObject ={};
var nameFn ='MyNewFunction';
var createFn = function(object,functionName, Body){
object[functionName]= new Function(Body);
}
createFn(globalObject,nameFn,"return (arguments[0] + arguments[1])/2");
You can call your new function:
globalObject[nameFn](10,20);
Result: 15
Please note that in body your function you can get params via collection arguments
window["foo"+"bar"] = function(){ console.log("foobar is called"); }
Here's a way to create a named proxy function that executes another function with the context you supply.
function createNamedProxy(name, fn, context) {
var template = [
'(function #name() {',
' #name.fn.apply(#name.context || window, arguments);',
'})'
].join('').replace(/#name/g, name),
result = eval(template);
result.fn = fn;
result.context = context;
return result;
}
// Example Usage
var anonymous = function() { alert( document === this ); },
named = createNamedProxy('Named', anonymous, document);
// Will alert 'true'
named();
The solution above creates a function that can create and return a named function that executed whatever you'd like. If you don't supply context, it will assume the window object just like a normal anonymous function would. To create the solution you wanted you would do:
var varName = 'ytp_1';
window[varName + '_StateManager'] =
createNamedProxy(varName + '_StateManager', function(newState) {
ytpStateHelper(varName, newState);
});
Where varName could be any programmatic prefix you'd like. When invoking ytp_1_StateManager() you would pass in your newState value and the code would call ytpStateHelper with your variable name and the newState.
Hope this helps.

Force missing parameters in JavaScript

When you call a function in JavaScript and you miss to pass some parameter, nothing happens.
This makes the code harder to debug, so I would like to change that behavior.
I've seen
How best to determine if an argument is not sent to the JavaScript function
but I want a solution with a constant number of typed lines of code; not typing extra code for each function.
I've thought about automatically prefixing the code of all functions with that code, by modifying the constructor of the ("first-class") Function object.
Inspired by
Changing constructor in JavaScript
I've first tested whether I can change the constructor of the Function object, like this:
function Function2 () {
this.color = "white";
}
Function.prototype = new Function2();
f = new Function();
alert(f.color);
But it alerts "undefined" instead of "white", so it is not working, so I've don't further explored this technique.
Do you know any solution for this problem at any level? Hacking the guts of JavaScript would be OK but any other practical tip on how to find missing arguments would be OK as well.
If a function of yours requires certain arguments to be passed, you should check for those arguments specifically as part of the validation of the function.
Extending the Function object is not the best idea because many libraries rely on the behavior of defaulting arguments that are not passed (such as jQuery not passing anything to it's scoped undefined variable).
Two approaches I tend to use:
1) an argument is required for the function to work
var foo = function (requiredParam) {
if (typeof requiredParam === 'undefined') {
throw new Error('You must pass requiredParam to function Foo!');
}
// solve world hunger here
};
2) an argument not passed but can be defaulted to something (uses jQuery)
var foo = function (argumentObject) {
argumentObject = $.extend({
someArgument1: 'defaultValue1',
someArgument2: 'defaultValue2'
}, argumentObject || {});
// save the world from alien invaders here
};
As others have said, there are many reasons not to do this, but I know of a couple of ways, so I'll tell you how! For science!
This is the first, stolen from Gaby, give him an upvote! Here's a rough overview of how it works:
//example function
function thing(a, b, c) {
}
var functionPool = {} // create a variable to hold the original versions of the functions
for( var func in window ) // scan all items in window scope
{
if (typeof(window[func]) === 'function') // if item is a function
{
functionPool[func] = window[func]; // store the original to our global pool
(function(){ // create an closure to maintain function name
var functionName = func;
window[functionName] = function(){ // overwrite the function with our own version
var args = [].splice.call(arguments,0); // convert arguments to array
// do the logging before callling the method
if(functionPool[functionName].length > args.length)
throw "Not enough arguments for function " + functionName + " expected " + functionPool[functionName].length + " got " + args.length;
// call the original method but in the window scope, and return the results
return functionPool[functionName].apply(window, args );
// additional logging could take place here if we stored the return value ..
}
})();
}
}
thing(1,2 ,3); //fine
thing(1,2); //throws error
The second way:
Now there is another way to do this that I can't remember the details exactly, basically you overrride Function.prototype.call. But as it says in this question, this involves an infinite loop. So you need an untainted Function object to call, this is done by a trick of turning the variables into a string and then using eval to call the function in an untainted context! There's a really great snippet out the showing you how from the early days of the web, but alas I can't find it at the moment. There's a hack that's required to pass the variables properly and I think you may actually lose context, so it's pretty fragile.
Still, as stated, don't try and force javascript to do something against its nature, either trust your fellow programmers or supply defaults, as per all the other answers.
You can imitate something like Python’s decorators. This does require extra typing per function, though not extra lines.
function force(inner) {
return function() {
if (arguments.length === inner.length) {
return inner.apply(this, arguments);
} else {
throw "expected " + inner.length +
" arguments, got " + arguments.length;
}
}
}
var myFunc = force(function(foo, bar, baz) {
// ...
});
In general this sounds like a bad idea, because you’re basically messing with the language. Do you really forget to pass arguments that often?
You could use the decorator pattern. The following decorator allows you to specify minimum and maximum number of arguments that need to be passed and an optional error handler.
/* Wrap the function *f*, so that *error_callback* is called when the number
of passed arguments is not with range *nmin* to *nmax*. *error_callback*
may be ommited to make the wrapper just throw an error message.
The wrapped function is returned. */
function require_arguments(f, nmin, nmax, error_callback) {
if (!error_callback) {
error_callback = function(n, nmin, nmax) {
throw 'Expected arguments from ' + nmin + ' to ' + nmax + ' (' +
n + ' passed).';
}
}
function wrapper() {
var n_args = arguments.length;
console.log(n_args, nmin, nmax);
console.log((nmin <= 0) && (0 <= nmax));
if ((nmin <= n_args) && (n_args <= nmax)) {
return f.apply(this, arguments);
}
return error_callback(n_args, nmin, nmax);
}
for (e in f) {
wrapper[e] = f[e];
}
return wrapper;
}
var foo = require_arguments(function(a, b, c) {
/* .. */
}, 1, 3);
foo(1);
foo(1, 2);
foo(1, 2, 3);
foo(1, 2, 3, 4); // uncaught exception: Expected arguments from 1 to 3 (4 passed).
foo(); // uncaught exception: Expected arguments from 1 to 3 (0 passed).

Categories

Resources