is there a way to call this javascript function in this way? - javascript

I'd like be able to call a function like item_edit.say hello passed as a string on the window object (like the last line of the following):
var arc={ view: { item_edit: {} } };
arc.view.item_edit={
say_hello: function(){
alert('hello there');
}
}
var f_name='say_hello';
var g_name='item_edit.say_hello';
var str=window.arc.view.item_edit[f_name](); // <- this works
var str2=window.arc.view[g_name](); // <- this is what I'm interested in; curently doesn't work
any ideas on how to get this to work?
thx in advance
edit #1
I guess I should add that probably don't want to be doing eval although the more I look at it, that might be what makes sense (and is in fact what eval was made to do).

Sure. The Google closure library does something like this in its goog.provide function when not optimized by the compiler.
function callDotted(obj, path, args) {
var parts = path ? path.split('.') : [];
var i, n = parts.length;
for (i = 0; i < n - 1; ++i) {
obj = obj[parts[i]];
}
var fn = i < n ? obj[parts[i]] : obj;
return fn.apply(obj, args);
}
and then on browsers where Date.now returns the current timestamp,
callDotted(window, 'Date.now', [])
returns the current timestamp.

Here's one way using .reduce().
var str2 = g_name.split('.').reduce(function(obj, key) {
return obj[key];
}, window.arc.view);
You'll need to shim it for older browsers, and introduce safety checks if you want.
If you do this a lot, I'd add the function to your library so you can reuse it.
function keyToObj(obj, key) {
return obj[key];
}
Then use it like this:
var str2 = g_name.split('.').reduce(keyToObj, window.arc.view);
As #MikeSamuel pointed out, there's an issue with the this value of the executed function when using this approach.
To resolve this, we could make another version that's suited specifically for method invocations.
function keyToMethod(obj, key, i, arr) {
return i === arr.length - 1 && typeof obj[key] === "function"
? function() {
return obj[key].apply(obj, arguments);
}
: obj[key];
}
Now our function returns a function that invokes the method from the proper object.
var str2 = g_name.split('.').reduce(keyToMethod, window.arc.view)();
We could further enhance the returned function to check to see if the this value is the default value, and use the provided value if not.

How about this:
var str2 = eval('window.arc.view.' + g_name + '()');

Related

Get object out of observable array

Why is m "undefined" in this code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for(var i=0;i<currentViewModel.availableReports().length;i++) {
if(currentViewModel.availableReports()[i].id == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
I call getReport() as an onclick event and I want to send the report object to a view (modal) I can do a foreach on the availableReports and it's all there. When I run through the debugger, it loops through the array and finds the right one. But why can't I pull it out of the array? "m" remains undefined the the function returns undefined.
What am I missing here?
EDIT: there is a follow up question here:
Can knockout.js wait to bind until an onClick?
You just need to change if(currentViewModel.availableReports()[i].id ... to if(currentViewModel.availableReports()[i].id() ... because after mapping id will become an observable, i.e. function.
Updated code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id() == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
Demo - Fiddle.
I'll repeat the solution from #NikolayErmakov's answer here, but want to add two things to get a more complete answer. You end with:
...m remains undefined and the function returns undefined.
What am I missing here?
You're missing two things:
The var m bit of the first statement inside the if is hoisted to the top of the current scope (the top of the function). This is why the debugger can tell you what m is, even if you never reach the line of code it's on.
If a function invocation reaches the end of a function (as is the case for you, since you never go inside the if) without seeing an explicit return statement, it will return undefined.
To better understand this, you should interpret your function like this:
currentViewModel.getReport = function(reportId) {
var m;
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id == reportId) {
m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
return undefined;
}
Some people (e.g. Douglas Crockford) do recommend placing var statements at the top of a function, though it's a matter of style to some degree. I don't think many people explicitly return undefined at the end of a function, though in your case I might be explicit about that scenario and return null (or throw an Error even).
As promised, I'll repeat the actual solution, as I concur with the other answer:
you need to invoke id as a function to get its value (because the mapping plugin will map to observable()s.
In addition:
I'd retrieve the array only once
I'd suggest using === instead of ==
Here's my v0.5 version:
currentViewModel.getReport = function(reportId) {
var m = null, reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
m = reports[i];
return m;
}
}
return m;
}
But I'd optimize it to this v1.0:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
return reports[i];
}
}
return null;
}
For completeness, here's another version that utilizes filter on arrays:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports().filter(function(r) { return r.id() === reportId; });
return reports.length >= 1 ? reports[0] : null;
}

How do I call a function stored in a variable that is of indeterminate depth

I looked at this:
Calling a JavaScript function named in a variable
But it doesn't answer my question.
This normally works:
window['class']['sub_class']['function_name'](data);
But now I'm trying to make a general function that can handle any depth:
function callbackFunction(callback, data){
//callback = a.b.c, or a.b, or a
callback = explode(callback);
//I need to be able to call callbackFunction and somehow callback and form their proper form below
window[callback.a](data);
//or
window[callback.a][callback.b](data);
//or
window[callback.a][callback.b][callback.c](data);
}
I believe the duplicate suggested by Bergi will only solve half of your problem. Since your final value will be a function, and since that function is a member of an object, you'll end up executing it in the wrong context (i.e., with the wrong this value).
I suggest you use something like this:
function getCallback(path) {
var arr = path.split('.');
var k;
var fn = window;
while(k = arr.shift()) {
if(typeof fn[k] === "function") {
fn = fn[k].bind(fn);
} else {
fn = fn[k];
}
}
if(typeof fn === "function") return fn;
return function(){};
}
http://jsfiddle.net/7CEd5/
Compare the value of this in the callback with what you get by using the answers to Convert string in dot notation to get the object reference.
You can chain references to objects/sub-objects/etc for however long you want. If you have a point-delimited string (e.g. "document.blah.blah2.method"), then you need to split it to individual tokens (e.g. ["document", "blah", "blah2", "method"]).
Then it's simply a matter of looping through the chain:
var c = window;
for (var i = 0; i < chain.length - 1; i++) {
c = c[chain[i]];
}
c[chain[chain.length-1]](some_arguments);

Javascript function that returns two different types of variables depending on input?

I'm trying to follow the rule and avoid repeating the same code.
I have this single function but depending on the input I want it to either return an array of objects or an object (not an array of just one object)
e.g.(the actual function is much longer and more elaborate than this one obviously, there are just the last few lines after a much longer calculation)
function (nameParameter, ageParameter, inputType)
{
if (inputType === "asObject")
{
var x = {};
x.name = nameParameter;
x.age = ageParameter;
return x;
}
else if (inputType === "asArray")
{
var y = [];
y.push(nameParameter);
y.push(ageParameter);
return y;
}
};
Is this possible and if so is it good practice? Is there some other way around it?
Otherwise I'll have to create two distinct function with almost the exact same code.
Don't do this. Implement one version and add a wrapper function that converts the the other format you may want. That way the caller always gets consistent behaviour, and theres still no code duplication.
function asObject(nameParameter, ageParameter)
{
//Lots of work here.
var x = {};
x.name = nameParameter;
x.age = ageParameter;
return x;
};
function asArray(nameParameter, ageParameter)
{
//Just defer to the other version and repack its response.
var o = asObject(nameParameter, ageParameter);
var y = [o.nameParameter,o.ageParameter ];
return y;
}
You can simplify your code by declaring the object and array with the values already set, but in my opinion if you have this strict type of coding it is not necessary to keep this function... Anyway, here is a simplified version:
function (nameParameter, ageParameter, inputType) {
var ret;
if (inputType === "asObject") {
ret = {
name: nameParameter,
age: ageParameter
};
} else if (inputType === "asArray") {
ret = [nameParameter, ageParameter];
}
return ret;
};
I left it without name and with a semicolon at the end because I guess it has been declared through a variable.
Yes; that will work fine.
Javascript is not strongly-typed; functions can return whatever they want, whenever they want.
if ( typeof inputType == 'object') {
//object part of code
} else {
//array part of code
}

Javascript chaining and variable substitute

I am trying to get javascript chaining to work using variable substitution. Not able to get it work. Help appreciated.
var Class = function() {
this.one = function() {
alert('one');
return this;
}
this.two = function() {
alert('two');
return this;
}
if (this instanceof Class) {
return this.Class;
} else {
return new Class();
}
}
var test = new Class();
// this works
test.one().two();
var func = '.one().two()';
// want to make this work
test[func];
there is no function with the name '.one().two()'
Try this,
test['one']()['two']();
Edit:
I believe you are using this for learning purpose only and not on production site.
Highly not recommended. You might want to try an array instead:
var funcs = ['one','two'];
for(var i = 0; i < funcs.length; i++) {
test[funcs[i]]();
}
you can then wrap this into a little function:
function callChain(obj, funcs)
{
for(var i = 0; i < funcs.length; i++) {
obj[funcs[i]]();
}
return obj;
}
Edit: If your chain is stored as a string: .one().two(), you can use the split & string functions to generate the array dynamically.
Well, what you are asking for is far from best practice - so I will give you an unpopular answer - use eval.
If your input is general code as string, you don't really have any other option (specifically when your functions have parameters - .one(1 + 0.5).two(new Date())).
For example, to your Class, add:
this.excecute = function(commands){
eval('this' + commands);
};
And then:
test.excecute('.one().two(4 * 5)');
Working example: http://jsbin.com/ipazaz/1/edit
This emits the warning "eval is evil" (jslint, I think) - but I do not believe functions can be evil.
Even worse, what if you had the string 'one(); two(4 * 5);'?
You can make that work as well, using with:
this.excecute = function(commands){
with(this){
eval(commands);
}
};
This has an extra warning: "Don't use 'with'" - They really have something against us today, don't they?
Working example: http://jsbin.com/ipazaz/2/edit
Thank you all for prompt help. I ended up settling upon Ben Rowe suggestion.
var funcs = ['one','two'];
for(var i = 0; i < funcs.length; i++) {
test[funcs[i]]();
}
It fitted my requirement nicely. Appreciate all for the help. You all are wonderful.
You could add a method to the constructor:
this.chain = function chain(){
if (arguments.length && /\./.test(arguments[0])) {
return chain.apply(this,arguments[0].split('.'));
}
var methods = [].slice.call(arguments),
method = methods.shift();
if(this[method] instanceof Function){
this[method].call(this);
}
if (methods.length){
chain.apply(this,methods);
}
return this;
}
// now you could do something like:
test.chain('one.two.one.two.two');
Or extend Object.prototype
Object.prototype.chain = function chain(){
if (arguments.length && /\./.test(arguments[0])) {
return chain.apply(this,arguments[0].split('.'));
}
var methods = [].slice.call(arguments),
method = methods.shift();
if(this[method] && this[method] instanceof Function){
this[method].call(this);
}
if (methods.length){
chain.apply(this,methods);
}
return this;
};
// usage
({one:function(){console.log('I am one');},
two:function(){console.log('I am two');}})
.chain('one.two.one.one.two.two.two.one.two');
I think a simpler approach is to use javascript's array reduce function.
I needed this for some dynamic jquery stuff I was writing. Once you have your array of chain-able methods you could easily do the following.
var methods = ['next', 'child', 'parent'];
var element = methods.reduce(function(method){
return $(selector)[method]();
});
console.log(element) //works! as all method names in methods array are applied and returned each iteration.
For my case the accepted answer did not work for me it seems to only return the passed obj and not the obj plus it's chained methods.

Is there any possibility to have JSON.stringify preserve functions?

Take this object:
x = {
"key1": "xxx",
"key2": function(){return this.key1}
}
If I do this:
y = JSON.parse( JSON.stringify(x) );
Then y will return { "key1": "xxx" }. Is there anything one could do to transfer functions via stringify? Creating an object with attached functions is possible with the "ye goode olde eval()", but whats with packing it?
json-stringify-function is a similar post to this one.
A snippet discovered via that post may be useful to anyone stumbling across this answer. It works by making use of the replacer parameter in JSON.stringify and the reviver parameter in JSON.parse.
More specifically, when a value happens to be of type function, .toString() is called on it via the replacer. When it comes time to parse, eval() is performed via the reviver when a function is present in string form.
var JSONfn;
if (!JSONfn) {
JSONfn = {};
}
(function () {
JSONfn.stringify = function(obj) {
return JSON.stringify(obj,function(key, value){
return (typeof value === 'function' ) ? value.toString() : value;
});
}
JSONfn.parse = function(str) {
return JSON.parse(str,function(key, value){
if(typeof value != 'string') return value;
return ( value.substring(0,8) == 'function') ? eval('('+value+')') : value;
});
}
}());
Code Snippet taken from Vadim Kiryukhin's JSONfn.js or see documentation at Home Page
I've had a similar requirement lately. To be clear, the output looks like JSON but in fact is just javascript.
JSON.stringify works well in most cases, but "fails" with functions.
I got it working with a few tricks:
make use of replacer (2nd parameter of JSON.stringify())
use func.toString() to get the JS code for a function
remember which functions have been stringified and replace them directly in the result
And here's how it looks like:
// our source data
const source = {
"aaa": 123,
"bbb": function (c) {
// do something
return c + 1;
}
};
// keep a list of serialized functions
const functions = [];
// json replacer - returns a placeholder for functions
const jsonReplacer = function (key, val) {
if (typeof val === 'function') {
functions.push(val.toString());
return "{func_" + (functions.length - 1) + "}";
}
return val;
};
// regex replacer - replaces placeholders with functions
const funcReplacer = function (match, id) {
return functions[id];
};
const result = JSON
.stringify(source, jsonReplacer) // generate json with placeholders
.replace(/"\{func_(\d+)\}"/g, funcReplacer); // replace placeholders with functions
// show the result
document.body.innerText = result;
body { white-space: pre-wrap; font-family: monospace; }
Important: Be careful about the placeholder format - make sure it's not too generic. If you change it, also change the regex as applicable.
Technically this is not JSON, I can also hardly imagine why would you want to do this, but try the following hack:
x.key2 = x.key2.toString();
JSON.stringify(x) //"{"key1":"xxx","key2":"function (){return this.key1}"}"
Of course the first line can be automated by iterating recursively over the object. Reverse operation is harder - function is only a string, eval will work, but you have to guess whether a given key contains a stringified function code or not.
You can't pack functions since the data they close over is not visible to any serializer.
Even Mozilla's uneval cannot pack closures properly.
Your best bet, is to use a reviver and a replacer.
https://yuilibrary.com/yui/docs/json/json-freeze-thaw.html
The reviver function passed to JSON.parse is applied to all key:value pairs in the raw parsed object from the deepest keys to the highest level. In our case, this means that the name and discovered properties will be passed through the reviver, and then the object containing those keys will be passed through.
This is what I did https://gist.github.com/Lepozepo/3275d686bc56e4fb5d11d27ef330a8ed
function stringifyWithFunctions(object) {
return JSON.stringify(object, (key, val) => {
if (typeof val === 'function') {
return `(${val})`; // make it a string, surround it by parenthesis to ensure we can revive it as an anonymous function
}
return val;
});
};
function parseWithFunctions(obj) {
return JSON.parse(obj, (k, v) => {
if (typeof v === 'string' && v.indexOf('function') >= 0) {
return eval(v);
}
return v;
});
};
The naughty but effective way would be to simply:
Function.prototype.toJSON = function() { return this.toString(); }
Though your real problem (aside from modifying the prototype of Function) would be deserialization without the use of eval.
I have come up with this solution which will take care of conversion of functions (no eval). All you have to do is put this code before you use JSON methods. Usage is exactly the same but right now it takes only one param value to convert to a JSON string, so if you pass remaning replacer and space params, they will be ignored.
void function () {
window.JSON = Object.create(JSON)
JSON.stringify = function (obj) {
return JSON.__proto__.stringify(obj, function (key, value) {
if (typeof value === 'function') {
return value.toString()
}
return value
})
}
JSON.parse = function (obj) {
return JSON.__proto__.parse(obj, function (key, value) {
if (typeof value === 'string' && value.slice(0, 8) == 'function') {
return Function('return ' + value)()
}
return value
})
}
}()
// YOUR CODE GOES BELOW HERE
x = {
"key1": "xxx",
"key2": function(){return this.key1}
}
const y = JSON.parse(JSON.stringify(x))
console.log(y.key2())
It is entirely possible to create functions from string without eval()
var obj = {a:function(a,b){
return a+b;
}};
var serialized = JSON.stringify(obj, function(k,v){
//special treatment for function types
if(typeof v === "function")
return v.toString();//we save the function as string
return v;
});
/*output:
"{"a":"function (a,b){\n return a+b;\n }"}"
*/
now some magic to turn string into function with this function
var compileFunction = function(str){
//find parameters
var pstart = str.indexOf('('), pend = str.indexOf(')');
var params = str.substring(pstart+1, pend);
params = params.trim();
//find function body
var bstart = str.indexOf('{'), bend = str.lastIndexOf('}');
var str = str.substring(bstart+1, bend);
return Function(params, str);
}
now use JSON.parse with reviver
var revivedObj = JSON.parse(serialized, function(k,v){
// there is probably a better way to determ if a value is a function string
if(typeof v === "string" && v.indexOf("function") !== -1)
return compileFunction(v);
return v;
});
//output:
revivedObj.a
function anonymous(a,b
/**/) {
return a+b;
}
revivedObj.a(1,2)
3
To my knowledge, there are no serialization libraries that persist functions - in any language. Serialization is what one does to preserve data. Compilation is what one does to preserve functions.
It seems that people landing here are dealing with structures that would be valid JSON if not for the fact that they contain functions. So how do we handle stringifying these structures?
I ran into the problem while writing a script to modify RequireJS configurations. This is how I did it. First, there's a bit of code earlier that makes sure that the placeholder used internally (">>>F<<<") does not show up as a value in the RequireJS configuration. Very unlikely to happen but better safe than sorry. The input configuration is read as a JavaScript Object, which may contain arrays, atomic values, other Objects and functions. It would be straightforwardly stringifiable as JSON if functions were not present. This configuration is the config object in the code that follows:
// Holds functions we encounter.
var functions = [];
var placeholder = ">>>F<<<";
// This handler just records a function object in `functions` and returns the
// placeholder as the value to insert into the JSON structure.
function handler(key, value) {
if (value instanceof Function) {
functions.push(value);
return placeholder;
}
return value;
}
// We stringify, using our custom handler.
var pre = JSON.stringify(config, handler, 4);
// Then we replace the placeholders in order they were encountered, with
// the functions we've recorded.
var post = pre.replace(new RegExp('"' + placeholder + '"', 'g'),
functions.shift.bind(functions));
The post variable contains the final value. This code relies on the fact that the order in which handler is called is the same as the order of the various pieces of data in the final JSON. I've checked the ECMAScript 5th edition, which defines the stringification algorithm and cannot find a case where there would be an ordering problem. If this algorithm were to change in a future edition the fix would be to use unique placholders for function and use these to refer back to the functions which would be stored in an associative array mapping unique placeholders to functions.

Categories

Resources