Pergunta

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];
Foi útil?

Solução

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.

Outras dicas

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.

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top