Implement cascading methods in JavaScript

4

In the book JavaScript: The Good Parts , Douglas Crockford explains Cascade's concept of cascading method. According to the author these methods are characterized by changing the state of the object without returning anything. Nested methods return this. Here is the example taken from Crockford's book:

getElement('myBoxDiv').
move(350, 150).
width(100).
height(100).
color('red').
border('10px outset').
padding('4px').
appendText("Please stand by").
on('mousedown', function (m) {
this.startDrag(m, this.getNinth(m));
}).
on('mousemove', 'drag').
on('mouseup', 'stopDrag').
later(2000, function ( ) {
this.
color('yellow').
setHTML("What hath God wraught?").
slide(400, 40, 200, 200);
}).
tip('This box is resizeable');

How to implement this pattern with JavaScript? Is there any good practice for this implementation?

    
asked by anonymous 06.12.2014 / 18:30

2 answers

6

JavaScript libraries often use this concept, MooTools is the most comprehensive case I believe.

To implement this idea in code, all methods you want to chain must return a new Type / Type that contains the methods it calls below.

An example with native JavaScript,

Without having to do anything new:

var string = 'olá mundo!';
var letras = string.split('').filter(function(letra){ return /[a-z]/i.test(letra); }).join('');

In this case what is going on?

We have a string as the starting point. Applying the method .split('') we no longer have a string and we have an array. So we changed from String Type to Object / Array Type . The product of this first method was:

["o", "l", "á", " ", "m", "u", "n", "d", "o", "!"]

This array will be the material / input that the next method will use. The next .filter() method will remove from this array array values that are not "simple" letters. The pruduto will be an array Type Array that will be passed to the .join() arrays method that will return a String .

In this example we use cascading, native code. (Demo online: link )

To apply the same concept to self-made code, you have to take into account that all the methods you call have to produce / return something that can be used in the next threaded method for the cascade to work.

The way to do this is to ensure that at the end of the method there is a return . Following this return comes what you want to return:

return variavelComProdutoParaOProximoMetodoConsumir;

By analyzing your code I see that it starts with a getter getElement('myBoxDiv') that returns an object / element of the DOM. From there, analyzing what is being done, I infer that all these methods have at their end return this; and thus all these methods are being applied to the same element with which the string / cascade has started.

An example of a code using the MooTools library:

(online demo here )

document.id('foo').getParent('div').setStyle('border', '2px solid #ccf').addEvent('click', function(){
   alert(this.get('text')); 
});

In this case document.id('foo') is a function that MooTools has added to the object document , which passes as a string , "foo" argument. This function / method fetches the DOM element with the ID foo and returns an object / DOM element.

Then .getParent('div') will rise in the DOM and return the first div you find. This new object / DOM element will be the product used in the next method. Thus .setStyle('border', '2px solid #ccf') will apply a line to the edge of the element.

This method for applying CSS returns the element itself that you have modified. Like its example, setStyle() does what is asked of it and returns the DOM element.

So the next method can be applied to this element.

Note that MooTools adds new methods to the prototype of Elements, so it is very useful to return the object this at the end of each method.

To make your own cascading code:

Here's an example, with the same functionality as my example in MooTools does:

var metodos = {
    objeto: null,
    getID: function (seletor) {
        var el = document.getElementById(seletor);
        this.objeto = el;
        return this;
    },
    getParent: function (tag) {
        tag = tag.toUpperCase();
        var elm = this.objeto, x = 0;
        while (elm.nodeName != tag && elm.nodeName != 'body') {
            elm = elm.parentNode;
        }
        this.objeto = elm;
        return this;
    },
    setStyle: function (propriedade, valor) {
        this.objeto.style[propriedade] = valor;
        return this;
    },
    addEvent: function (tipo) {
        this.objeto.addEventListener(tipo, function () {
            alert('Estou a funcionar!');
        });
        return this;
    }
}
metodos.getID('foo').getParent('div').setStyle('border', '2px solid #ccf').addEvent('click');
div {
    padding: 10px;
}
<div>
    <div><span>Span 1</span>

    </div>
    <div><span id="foo">Span 2 (pode clicar aqui...)</span>

    </div>
</div>
    
06.12.2014 / 21:33
9

The function should return this . Here is an example:

function criarPessoa(nome) {
    return {
        nome: nome,
        idade: 25,
        alterarNome: function(nome) {
            this.nome = nome;
            return this;
        },
        alterarIdade: function(idade) {
            this.idade = idade;
            return this;
        }
    };
}

var pessoa = criarPessoa("Maria").alterarIdade(30).alterarNome("Paulo");
alert("Nome: " + pessoa.nome + "; Idade: " + pessoa.idade);
    
06.12.2014 / 19:09