1

I'm trying to create chaining with the javascript methods similar to what we have with jquery. Please let me know how to implement chaining with javascript.

var controller = {
    currentUser: '',
    fnFormatUserName: function(user) {
        this.currentUser = user;
        return this.currentUser.toUpperCase();
    },
    fnCreateUserId: function() {
        return this.currentUser + Math.random();
    }
}
var output = controller.fnFormatUserName('Manju').fnCreateUserId();
Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82
Manju
  • 2,520
  • 2
  • 14
  • 23

3 Answers3

2

As I already explained, since you are returning a string from fnFormatUserName you cannot use it for chaining.

To enable chaining, you need to return the object which invoked method. So, you cannot use getter methods for chaining.

In your example, the way to handle it is to have getter methods and methods with updates the object which can be used for chaining like

var controller = {
  currentUser: '',
  fnFormatUserName: function(user) {
    this.currentUser = user.toUpperCase();
    return this;
  },
  fnCreateUserId: function() {
    this.userId = this.currentUser + Math.random();
    return this;
  },
  getUserId: function() {
    return this.userId;
  }
}
var output = controller.fnFormatUserName('Manju').fnCreateUserId().getUserId();
document.body.innerHTML = output;

Another version could be

var controller = {
  currentUser: '',
  fnFormatUserName: function(user) {
    if (arguments.length == 0) {
      return this.currentUser;
    } else {
      this.currentUser = user.toUpperCase();
      return this;
    }
  },
  fnCreateUserId: function() {
    this.userId = this.currentUser + Math.random();
    return this;
  },
  getUserId: function() {
    return this.userId;
  }
}
var output = controller.fnFormatUserName('Manju').fnCreateUserId().getUserId();
r1.innerHTML = output;
r2.innerHTML = controller.fnFormatUserName();
<div id="r1"></div>
<div id="r2"></div>
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
  • Thanks Arun, The solution worked fine. But is there a way like i can create a method within an object such that it can be used as a single method during a method call and also used for method chaining – Manju Jan 20 '16 at 11:38
  • @Manju there are, but there should be some way for the method to know whether to return the object itself or some other value.... jQuery uses such a pattern with different methods, where it uses the same method as a setter and getter, where when setting value(ie when an argument is passed to it) it will return the object which can be used for chaining where as when used as a getter it will just return a string/whatever the value is which can't be used for chaining – Arun P Johny Jan 20 '16 at 11:43
0

You can use proxies to decorate methods so that they will return the object itself ("this") instead of the actual method return value. Below is an implementation of a chainer function that will do just that for any object. The code also declares a special symbol "target" which can be used to access the original object (and unaltered method return values), discarding the chaining proxy.

const target = Symbol('Symbol for the target of the chainer proxy');
const targetSymbol = target;
const chainer = (target) =>
  new Proxy(target, {
    get: (_, prop, receiver) =>
      prop === targetSymbol
        ? target
        : typeof target[prop] === 'function'
        ? new Proxy(target[prop], {
            apply: (f, _, args) => {
              f.apply(target, args);
              return receiver;
            },
          })
        : target[prop],
  });

const controller = {
    currentUser: '',
    fnFormatUserName: function(user) {
        return this.currentUser = user.toUpperCase();
    },
    fnCreateUserId: function() {
        return this.currentUser + Math.random();
    }
}


const output = chainer(controller).fnFormatUserName('Manju')[target].fnCreateUserId();

console.log(output);

Another option would be that the decorated methods would always return an intermediate object with two properties: the this context ("this") and a reference to the original object with undecorated methods ("target"). See below.

const chainer = (target) =>
  new Proxy(target, {
    get: (_, prop, receiver) =>
        typeof target[prop] === 'function'
        ? new Proxy(target[prop], {
            apply: (f, _, args) => {
              f.apply(target, args);
              return {
                this: receiver,
                target,
              }
            },
          })
        : target[prop],
  });

const counter = {
  value: 0,
  increment: function() { 
    return ++this.value;
  }
}

const value = chainer(counter)
    .increment().this
    .increment().target
    .increment();

console.log(value);
loop
  • 825
  • 6
  • 15
0

I suppose this might be seen as "cheating", but you could very easily achieve a similar result by extending the String.prototype like:

String.prototype.upper=function(){return this.toUpperCase()};
String.prototype.makeId=function(){return this+Math.random()};

// test

const str="abc";

console.log(str.upper().makeId(), str);

It will, of course, change the behaviour of all strings in the current session as they will now have the additional methods .upper() and .makeId() associated with them.

Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43