Prototypes and callbacks - javascript

I'm now in the process of transforming several functions into prototypes and I'm stuck on callbacks.
Below is a minimal example of what I want to achieve:
WebSocketClient.prototype.send = function(t, data)
{
this.ws.send(data);
this.ws.onmessage = function(evt)
{
var msg = evt.data;
var jsonData = JSON.parse(msg);
if(jsonData["callback"] !== 'undefined' && jsonData["callback"] !== "") // jsonData = {callback:"on_test", data:[0,1,2]}
{
// How to transform callback into call ???
var fn = window[jsonData["callback"]]; // == undefined
if(typeof fn === 'function')
fn(jsonData["data"]);
}
};
};
function Test()
{
this.wc = new WebsocketClient();
// here ws.connect, etc.
}
Test.prototype.send = function()
{
this.wc.send(test, '{request:"get_data", callback:"on_test"')
}
Test.prototype.on_test = function(arr)
{
// ...
}
var test = new Test();
test.send();
I want to make a call to t.callback(data) but can't figure out how to do this?
I tried:
window[jsonData["callback"]]; // == undefined
window['Test.prototype.' + jsonData["callback"]]; // == undefined
window['Test.' + jsonData["callback"]]; // == undefined

There must be an error here:
Test.prototype.send = function()
{
// use 'this' instead of 'test'
// this.wc.send(test, '{request:"get_data", callback:"on_test"')
this.wc.send(this, '{request:"get_data", callback:"on_test"')
}
And since on_test() is defined on Test.prototype, call it this way:
WebSocketClient.prototype.send = function(t, data)
{
this.ws.send(data);
this.ws.onmessage = function(evt)
{
var msg = evt.data;
var jsonData = JSON.parse(msg);
if(jsonData["callback"] !== 'undefined' && jsonData["callback"] !== "") // jsonData = {callback:"on_test", data:[0,1,2]}
{
var fn = t[jsonData["callback"]]; // t will be available in this scope, because you've created a closure
if(typeof fn === 'function') {
fn(jsonData["data"]);
// OR, preserving scope of Test class instance t
fn.call(t, jsonData["data"]);
}
}
};
};
UPDATE: And be wary, that by calling fn(jsonData["data"]); you are loosing the original scope of the method. This way, this inside the on_test() method will point to global scope. If this is undesired, use call() (see corrected above).

If your function is in the global scope you could use
window.call(this, 'functionName', arguments)
In your case,
window.call(this, jsonData['callback'], jsonData['data'])
By doing that, the callback will be invoked with jsonData['data'] as a parameter.
If the function is within an object test, just use
test.call(this, 'on_test', jsonData['data'])

Related

Each loop to check if function exists

I am developing a site using a third-party CMS and I have to include functions across various parts of the content depending on which page is being displayed. To reduce the amount of functions being called on each page load, I would like to loop through an array of functions to check if they exist before firing them.
This single function would then be called at body onload.
I have adapted code from Javascript Array of Functions and How to implement an array of functions in Javascript? as well as isFunction.
My understanding was that I could put the functions in an array without () and they would not be called but in my console in Chrome an Uncaught Reference error is generated on the line in the array where a function name is mentioned.
e.g. the jb_underimage_height function is not in the code on all pages, so this generates the error when it does not exist.
Here is the code so far:
function jb_onloads() {
var functionArray = [
jb_category_price_POA,
jb_highlight_tech_columns,
jb_underimage_height,
jb_moveGuestButton,
jb_loginCheck,
jb_moveRefineSpan,
jb_style_from_url,
materials_dropdown,
jb_remove_search_spaces,
jb_init_social,
checkCookies,
jb_category_change_class,
jb_move_basket_text,
jb_style_form,
jb_checkNotifyEnvelope
]; // end of functionArray
$.each(functionArray, function(key, value) {
if(typeof functionArray[key] !== 'undefined' && typeof functionArray[key] === "function") { functionArray[key](); }
});
} // end of jb_onloads
And this was my workaround when I had to this.
function a() { alert ("I am a") };
function b() { alert ("I am b") };
var arr = [
typeof a === "function" && a || 0,
typeof b === "function" && b || 0,
typeof c === "function" && c || 0
];
arr.forEach(function(func) {
if(typeof func === "function") {
func();
}
});
maybe we can do it as:
1 function defining:
if (typeof myFuncCollections == "undefined") // window.myFuncCollections
myFuncCollections = {};
myFuncCollections.func1 = function func1() {
console.log("func1");
};
//or
myFuncCollections['funcname'] = function funcname() {
console.log("funcname");
}
....
2 jb_onloads()
function jb_onloads() {
if (typeof myFuncCollections == "undefined")
myFuncCollections = {};
$.each(myFuncCollections, function(i) {
myFuncCollections[i]();
});
}
3 call jb_onloads() after including 1 and 2. And That do not require inlcuding 1-script before 2-script. Also, your can use any function in 1-script outside jb_onloads after including 1-script.
Since using Global value, please use special prefix for naming your "myFuncCollections"
You are trying to insert function references to the array. But if the function is not defined then that name does not exists and thus the error.
Add them as strings
function jb_onloads() {
var functionArray = [
'jb_category_price_POA',
'jb_highlight_tech_columns',
'jb_underimage_height',
'jb_moveGuestButton',
'jb_loginCheck',
'jb_moveRefineSpan',
'jb_style_from_url',
'materials_dropdown',
'jb_remove_search_spaces',
'jb_init_social',
'checkCookies',
'jb_category_change_class',
'jb_move_basket_text',
'jb_style_form',
'jb_checkNotifyEnvelope'
]; // end of functionArray
$.each(functionArray, function(index, functionName) {
// assuming functions are in the global scope
var func = window[ functionName ],
funcType = typeof func;
if (funcType === "function") {
func();
}
});
} // end of jb_onloads

jquery making correct callback

how to correctly make callback in jquery plugin.
(function($) {
var parameter = {
first:'1',
second:'2',
call: $.noop
};
var something = 'yes';
var testf = function(){
// i neeed launch callback here;
var something_else = something + 'no';
alert(something_else)
}
$.fn.sadstory = function(options) {
if (options && typeof options === 'object')
{
$.extend(parameter, options);
}
testf();
return this;
}
})(jQuery);
and i need atccess var and owerwrite or making somthing else with him.
$('elm').sadstory({
call: function(){
this.something = 'no';
}
});
and result would by alert box with text nono instead of yesno, now to make this callback correctly.
i think you can do it like that:
$.fn.sadstory = function(options,callback) {
if (options && typeof options === 'object')
{
$.extend(parameter, options);
}
testf();
// example, var c is passed to callback function
var c= "abc";
callback(c);
return this;
}
you can call like
.sadstory({..},function(c) {
console.log(c) // logs "abc"
})
should also work as property of options
this.something doesn't exist. The only something is a variable with the scope of your testf method.
A solution is to pass an object as a parameter to the callback, and allow the callback to modify this object.
(function($) {
var parameter = {
first:'1',
second:'2',
call: $.noop
};
var something = 'yes';
var testf = function(){
// Initialize the string to a default value
var stringGenerationParams = { something: 'yes' };
// Allow the callback to modify the string generation params
parameter.call(stringGenerationParams);
// At this point, stringGenerationParams.something may have been
// modified by the callback function
var something_else = stringGenerationParams.something + 'no';
alert(something_else)
}
$.fn.sadstory = function(options) {
if (options && typeof options === 'object')
{
$.extend(parameter, options);
}
testf();
return this;
}
})(jQuery);
And now, this will work:
$('elm').sadstory({
call: function(e) {
e.something = 'no';
}
});

Javascript scoping for object

I get that everything in Javascript is an object, but how is possible to declare one variable in one scope; I mean merely as a variable and suddenly start using it as an object assigning some properties "INSIDE" other functions. How does scoping for this case work?
https://github.com/ariya/esprima/blob/master/esprima.js
In this code, the extra variable is only declared without being given any properties:
var Token,
extra;
And suddenly it starts being used by object as follows:
function addComment(type, value, start, end, loc) {
var comment, attacher;
assert(typeof start === 'number', 'Comment must have valid position');
// Because the way the actual token is scanned, often the comments
// (if any) are skipped twice during the lexical analysis.
// Thus, we need to skip adding a comment if the comment array already
// handled it.
if (state.lastCommentStart >= start) {
return;
}
state.lastCommentStart = start;
comment = {
type: type,
value: value
};
if (extra.range) {
comment.range = [start, end];
}
if (extra.loc) {
comment.loc = loc;
}
extra.comments.push(comment);
if (extra.attachComment) {
attacher = {
comment: comment,
leading: null,
trailing: null,
range: [start, end]
};
extra.pendingComments.push(attacher);
}
}
The closest example that I can get for this extra to be initiated as object is from the following function:
function tokenize(code, options) {
var toString,
token,
tokens;
toString = String;
if (typeof code !== 'string' && !(code instanceof String)) {
code = toString(code);
}
delegate = SyntaxTreeDelegate;
source = code;
index = 0;
lineNumber = (source.length > 0) ? 1 : 0;
lineStart = 0;
length = source.length;
lookahead = null;
state = {
allowIn: true,
labelSet: {},
inFunctionBody: false,
inIteration: false,
inSwitch: false,
lastCommentStart: -1
};
extra = {};
// Options matching.
options = options || {};
// Of course we collect tokens here.
options.tokens = true;
extra.tokens = [];
extra.tokenize = true;
// The following two fields are necessary to compute the Regex tokens.
extra.openParenToken = -1;
extra.openCurlyToken = -1;
extra.range = (typeof options.range === 'boolean') && options.range;
extra.loc = (typeof options.loc === 'boolean') && options.loc;
if (typeof options.comment === 'boolean' && options.comment) {
extra.comments = [];
}
if (typeof options.tolerant === 'boolean' && options.tolerant) {
extra.errors = [];
}
if (length > 0) {
if (typeof source[0] === 'undefined') {
// Try first to convert to a string. This is good as fast path
// for old IE which understands string indexing for string
// literals only and not for string object.
if (code instanceof String) {
source = code.valueOf();
}
}
}
try {
peek();
if (lookahead.type === Token.EOF) {
return extra.tokens;
}
token = lex();
while (lookahead.type !== Token.EOF) {
try {
token = lex();
} catch (lexError) {
token = lookahead;
if (extra.errors) {
extra.errors.push(lexError);
// We have to break on the first error
// to avoid infinite loops.
break;
} else {
throw lexError;
}
}
}
filterTokenLocation();
tokens = extra.tokens;
if (typeof extra.comments !== 'undefined') {
tokens.comments = extra.comments;
}
if (typeof extra.errors !== 'undefined') {
tokens.errors = extra.errors;
}
} catch (e) {
throw e;
} finally {
extra = {};
}
return tokens;
}
But still it is only inside this function, not in the same scope as
var Token,
extra;
How is it possible just to declare a variable and only instantiate the properties inside a function? How is it shared between different scopes? Once given the properties in one function, is it shared to other scopes such as another scope in another function? So confusing.
Line #3658 sets it to an empty object:
extra = {};
Scoping has to do with the matching of identifiers (i.e. names) to the what they refer to. For an object created in one scope to be accessed from another, you merely need to have a name in each refer to the same thing (for example, by returning an object created in a function to its caller and having the caller assign it to a variable whose name is in its scope).

javascript setting custom error handler to 3rd party plugins or modules

I am trying to set a custom error handler for 3rd party plugins/modules in my core library, but somehow, myHandler does not alert the e.message.
Can somebody help me please? thank you
Function.prototype.setErrorHandler = function(f) {
if (!f) {
throw new Error('No function provided.');
}
var that = this;
var g = function() {
try {
var a = [];
for(var i=0; i<arguments.length; i++) {
a.push(arguments[i]);
}
that.apply(null,a);
}
catch(e) {
return f(e);
}
};
g.old = this;
return g;
};
function myHandler(e) {
alert(e.message)
};
// my Core library object
(function(){
if (typeof window.Core === 'undefined') {
var Core = window.Core = function() {
this.addPlugin = function(namespace, obj){
if (typeof this[namespace] === 'undefined') {
if (typeof obj === 'function') {
obj.setErrorHandler(myHandler);
} else if (!!obj && typeof obj === 'object') {
for (var o in obj) {
if (obj.hasOwnProperty(o) && typeof obj[o] === 'function') {
obj[o].setErrorHandler(myHandler);
}
}
}
this[namespace] = obj;
return true;
} else {
alert("The namespace '" + namespace + "' is already taken...");
//return false;
}
};
};
window.Core = new Core();
}
})();
// test plugin
(function(){
var myPlugin = {
init: function() {},
conf: function() {
return this.foo.x; // error here
}
};
Core.addPlugin("myPlugin", myPlugin);
})();
// test
Core.myPlugin.conf(); // supposed to alert(e.message) from myHandler()
setErrorHandler in the above code doesn't set an error handler on a Function, as such. JavaScript does not give you the ability to change the called code inside a Function object.
Instead it makes a wrapped version of the function it's called on, and returns it.
obj.setErrorHandler(myHandler);
Can't work as the returned wrapper function is thrown away, not assigned to anything.
You could say:
obj[o]= obj[o].setErrorHandler(myHandler);
though I'm a bit worried about the consequences of swapping out functions with different, wrapped versions. That won't necessarily work for all cases and could certainly confuse third-party code. At the least, you'd want to ensure you don't wrap functions twice, and also retain the call-time this value in the wrapper:
that.apply(this, a);
(Note: you don't need the manual conversion of arguments to an Array. It's valid to pass the arguments object directly to apply.)

How can I make a function defined in jQuery.ready available globally?

I have a function that strips the youtube id off a url. I then want to use this function 10 time per page (in the wordpress loop).
The function works great when I feed it the url within my function script tags, but when I start a new set of script tags within the loop, it does not work.
I need to know how I can use my function without declaring it all first.
So this is the code I have in the header:
<script type="text/javascript">
$(document).ready(function() {
var getList = function(url, gkey){
var returned = null;
if (url.indexOf("?") != -1){
var list = url.split("?")[1].split("&"),
gets = [];
for (var ind in list){
var kv = list[ind].split("=");
if (kv.length>0)
gets[kv[0]] = kv[1];
}
returned = gets;
if (typeof gkey != "undefined")
if (typeof gets[gkey] != "undefined")
returned = gets[gkey];
}
return returned;
};
// THIS WORKS
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
});
But when I try use this somewhere else on the page, it doesnt work.
<script type="text/javascript">
$(document).ready(function() {
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
};
</script>
Firebug gives me getList is not defined which makes sense, because its not. Am I able to 'globally' declare this function?
You have two options, add it to the window object to make it global:
window.getList = function(url, gkey){
// etc...
}
or move it from inside the document ready event handler into the global scope:
$(document).ready(function() {
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
});
var getList = function(url, gkey){
var returned = null;
if (url.indexOf("?") != -1){
var list = url.split("?")[1].split("&"),
gets = [];
for (var ind in list){
var kv = list[ind].split("=");
if (kv.length>0)
gets[kv[0]] = kv[1];
}
returned = gets;
if (typeof gkey != "undefined")
if (typeof gets[gkey] != "undefined")
returned = gets[gkey];
}
return returned;
};
You might also want to read this question about using var functionName = function () {} vs function functionName() {}, and this article about variable scope.
Yet another option is to hang the function off the jQuery object itself. That way you avoid polluting the global name space any further:
jQuery.getlist = function getlist(url, gkey) {
// ...
}
Then you can get at it with "$.getlist(url, key)"
declare getList() outside the ready() function..
var getList = function(url, gkey){
var returned = null;
if (url.indexOf("?") !=
....
....
...
};
Now the getList will work anywhere in the code:
$(document).ready( function() {
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
});
The problem was, scope of the getList(.) function.
You can simply add your function in the $.fn variable:
(function ($) {
$.fn.getList = function() {
// ...
};
}(jQuery));
Example usage:
$().getList();
This is what you would typically do while creating a Basic Plugin for jQuery.
Just define it as a regular function at the top of your script:
<script type="text/javascript">
function getlist(url, gkey){
...
}
</script>
To declare it as a global function, just get rid of all the jQuery specific bits. Something like this:
function getList(url, gkey) {
var returned = null;
if (url.indexOf("?") != -1){
var list = url.split("?")[1].split("&"), gets = [];
for (var ind in list){
var kv = list[ind].split("=");
if (kv.length>0) {
gets[kv[0]] = kv[1];
}
}
returned = gets;
if (typeof gkey != "undefined") {
if (typeof gets[gkey] != "undefined") {
returned = gets[gkey];
}
}
return returned;
}
And then you should be able to call it from anywhere.

Categories