Documentation

CocktailJS API Reference

v0.5.0

Table of Contents

cocktail.mix(subject, options);

Since v0.0.1

Merges the options object into the subject. If subject is a class, then the merge will be applied to the subject's prototype. The second parameter can specify annotations that will trigger some processes over the current mix.

subject {Function|Object}

This is the variable where the mix will be done. It is usually a variable name, where we can reference a class or object:

options {Object}

The option object can contain any number of methods, properties and annotations. All the properties and methods will be merged into the subject.

cocktail.use(AnnotationDefinition);

Since v0.5.0

Registers the given AnnotationDefinition with the current cocktail instance.

AnnotationDefinition {Class}

This is the Processor Class defined with @annotation. See @annotation for more details about defining a custom annotation processor.

Example

index.js

var cocktail = require('cocktail'),
    myObj = {};

// 1.-include Custom annotation definition
var Custom = require('./Custom.js');

// 2.-register with cocktail instance
cocktail.use(Custom);

cocktail.mix(myObj, {
    // 3.-annotate the code with the custom annotation  
    '@custom' : 'some-custom-parameter',

    //more annotations, or code
});

Annotations

Annotations are special properties defined in the options parameter of cocktail.mix() method. This properties are defined by a name starting with @. The following is the list of the current cocktail core annotations.

@annotation: {String} name

Aplicable to Class

Since v0.5.0

This annotation will register the current mix as a custom annotation by the name parameter precedeed by the @symbol. The annotation is applicable to a Class with a process and setParameter methods.

A processor class should implement the following methods and properties:

setParameter({Any})

The setParameter will be used to apply the argument passed to the annotation in the mix execution.

process(subject, proto)

This method will be executed in the processor with the given parameters. Here is where the annotation processor takes place to perform the task over the subject and/or proto. The subject is the first parameter passed in the mix() method and the proto is the second one without the annotations.

priority: {Number}

The priority defines the execution sequence in the queue.

Execution Sequence

In order to execute the different annotations CocktailJS uses an execution queue based in priorities. These priorities are defined into cocktail.SEQUENCE and you can define when your annotation will be executed by defining a priority property into your Annotation Class Processor. The priority property should be a Number. The following list shows the predifined priorities:

The order in the execution queue is given by the values in the priority property. The lower value will be executed first except for -1 (NO_OP).

Note: An Annotation Class Processor with no priority defined will be executed with no prority by default meaning that it will be the last one to be executed.

For each priority (except for NO_OP) there are defined a PRE_XXX and POST_XXX as helpers in case you want to execute the annotation right after or before some predefined stage.

Example:

Custom.js

var cocktail = require('cocktail'),
    Custom = function(){};

cocktail.mix(Custom, {
    '@annotation' : 'custom',

    setParameter: function(){/*...*/},

    process: function(subject){/*...*/}
});

module.exports = Custom;

The Custom class is a processor for the annotation named custom. Now we can use our newly created annotation in our code:

index.js

var cocktail = require('cocktail'),
    myObj = {};

// 1.-include Custom annotation definition
var Custom = require('./Custom.js');

// 2.-register with cocktail instance
cocktail.use(Custom);

cocktail.mix(myObj, {
    // 3.-annotate the code with the custom annotation  
    '@custom' : 'some-custom-parameter',

    //more annotations, or code
});

In the index.js file we are first requiring the annotation definition and registering it with the cocktail instance. Then we can use our custom annotation into our code. The parameter specified in the mix for @custom is the one that will be passed to setParameter method in Custom Annotation definition. Then the process() receives the subject, myObj in this case, when it is processed by the annotation.

@merge: {String} merge strategy

Aplicable to Class, Object, Trait, Talent

Since v0.0.4

The merge strategy defines how the properties into the subject will be merged. The default strategy single is applied everytime you execute a mix(). The single strategy is equivalent to mine.

As convention for merge strategies the second parameter is mine and the subject is their

Merge Strategies

The following strategies are available to use as @merge annotation parameter:

Example:

index.js

var cocktail = require('cocktail'),
    myObject = {
        property: {
            a: 'a',
            b: 'b'
        },
        values: [1,2]
    };

cocktail.mix(myObject, {
    property: {
        z: 'z'
    }

});

Here the default merge strategy is applied (mine). So the myObject.property becomes {z: 'z'}

index.js

var cocktail = require('cocktail'),
    myObject = {
        property: {
            a: 'a',
            b: 'b'
        },
        values: [1,2]
    };

cocktail.mix(myObject, {
    '@merge': 'their',
    property: {
        z: 'z'
    }

});

Here the their merge strategy is applied. So the myObject.property becomes {a: 'a', b: 'b'}

index.js

var cocktail = require('cocktail'),
    myObject = {
        property: {
            a: 'a',
            b: 'b'
        },
        values: [1,2]
    };

cocktail.mix(myObject, {
    '@merge': 'deep-mine',
    property: {
        a: 'A',
        z: 'z'
    },
    values: [3,4]

});

Here the deep-mine merge strategy is applied. So the myObject.property becomes {a: 'A', b: 'b', z: 'z'} and myObject.values gets concatenated becoming [1,2,3,4].

index.js

var cocktail = require('cocktail'),
    myObject = {
        property: {
            a: 'a',
            b: 'b'
        },
        values: [1,2]
    };

cocktail.mix(myObject, {
    '@merge': 'deep-their',
    property: {
        a: 'A',
        z: 'z'
    },
    values: [3,4]

});

Here the deep-their merge strategy is applied. So the myObject.property becomes {a: 'a', b: 'b', z: 'z'} and myObject.values gets concatenated becoming [1,2,3,4].

@extends: {Function} parent class

Aplicable to Class

Since v0.0.1

Makes the subject inherit from the given parent class with a prototype chaining inheritance. The parent overriden class methods are accessible through a callSuper method.

callSuper({String} methodName, {Any} param1, ..., {Any} paramN)

It calls the specified methodName on the parent class with the given params.

Example:

MyClass.js

var cocktail = require('cocktail'),
    Base = require('./Base'),
    MyClass = function(){};

cocktail.mix(MyClass, {
    '@extends' : Base,

    /**
     * overrides Base foo(param) method
     */
    foo: function(param){
        //do something here
        //...

        //call parent foo method
        this.callSuper('foo', param);
    }

    /* ... */

});

module.exports = MyClass;

MyClass class extends from the Base class.

index.js

var MyClass = require('./MyClass'),
    myObj;

myObj = new MyClass();
//...

myObj.foo('blah'); // this will call foo in MyClass and on its parent class

@properties: {Object} properties

Aplicable to Class, Object

Since v0.0.1

This is a helper to define getters and setters for the given properties. All the properties specified here become part of the subject (class prototype or object) with the specified value. For any non boolean properties the get[PropertyName] and set[PropertyName] methods are created. If the property is a boolean then an is[PropertyName] method is created instead of the getter.

Example

var cocktail = require('cocktail'),
    MyClass = function(){};

cocktail.mix(MyClass, {
    '@properties' : {
        foo: 'foo',
        total: 0,
        initialized: false,
        other: undefined
    },


    /**
     * overrides the setter for foo
     */
    setFoo: function(param){
        //do something here
    }

    /* ... */

}); 


var instance = new MyClass();

//returns 'foo'
instance.getFoo();


//returns false
instance.isInitialized();

//returns undefined
instance.getOther();

MyClass is defined with all the properties specified in the annotation.

@static: {Object} static methods and properties

Aplicable to Class

Since v0.4.0

This annotation will create the given methods and properties as static members of the given class.

Example

var cocktail = require('cocktail'),
    MyClass  = function(){},

cocktail.mix(MyClass, {
    '@static' : {
        someStaticMethod: function() {
            // static method ...
            return 'Hello from static!';
        },

        VALUE: 'SOME SORT OF VALUE'
    },

    foo: function(param){
        //do something here
        //...
    }

    /* ... */

});


console.log(MyClass.someStaticMethod());
// -> 'Hello from static!'
console.log(MyClass.VALUE);
// -> 'SOME SORT OF VALUE'

As static methods they cannot access the instance properties or methods. In a static method the keyword this points to the static part of the Class. In the previous example you can access the static property VALUE from within the someStaticMethod through this.VALUE

var cocktail = require('cocktail'),
    MyClass  = function(){},

cocktail.mix(MyClass, {
    '@static' : {

        someStaticMethod: function() {

            return this.VALUE;
        },

        VALUE: 'SOME SORT OF VALUE'
    },

    foo: function(param){
        //do something here
        //...
    }

    /* ... */

});


console.log(MyClass.someStaticMethod());
// -> 'SOME SORT OF VALUE'
console.log(MyClass.VALUE);
// -> 'SOME SORT OF VALUE'

@traits: {Array} trait list

Aplicable to Class, Trait, Talent

Since v0.0.1

This annotation allows to define or specify which traits will be part of the current mix. A Trait is an special case of class where you define only behavior that will be shared among other classes or even other Traits.

Traits are Composable Units of Behaviour (You can read more from this paper). Basically, a Trait is a Class, let's say a special type of Class, that has only behaviour (methods) and no state.

A Trait can be composed with other Traits. In cocktail you cannot extend from a Trait or a Trait cannot extend from other class.

Traits implementation doesn't allow to override a method defined in the target class with one in the current Trait but, it defines some mechanisms to avoid method collision:

Basically, all the methods defined in the Trait will become part of the target class.

Example

var cocktail = require('cocktail'),
    TraitA = require('./TraitA'),
    TraitB = require('./TraitB'),
    MyClass = function(){};

cocktail.mix(MyClass, {
    '@traits' : [
        TraitA,
        {
            trait: TraitB,

            //exclude foo method
            excludes: ['foo'],

            //and make myDoSmth alias on doSmth method from TraitB
            alias: {
                doSmth: 'myDoSmth' 
            }
        }
    ],

    foo: function(param){
        //do something here
        //...
    }

    /* ... */

});

@talents: {Array} talent list

Aplicable to Object

Since v0.0.1

Talents are very similar to Traits, in fact a Trait can be applied as a Talent in cocktail. The main difference is a Talent can be applied to an instance. So we can define a Talent as a Dynamically Composable Unit of Reuse (you can read more from this paper).

Talents share the same design principles that Traits and they have the same mechanisms (excludes and alias) to avoid method collision as in Traits.

Example

var cocktail = require('cocktail'),
    TraitA = require('./TraitA'),
    TraitB = require('./TraitB'),
    ClassA = require('./ClassA'),
    myObj = new ClassA();

cocktail.mix(myObj, {
    '@talents' : [
        TraitA,
        {
            talent: TraitB,

            //exclude foo method
            excludes: ['foo'],

            //and make myDoSmth alias on doSmth method from TraitB
            alias: {
                doSmth: 'myDoSmth' 
            }
        }
    ]

    /* ... */

});

@requires: {Array} required method list

Aplicable to Trait, Talent

Since v0.0.1

Traits and Talents definitions have no state but, they can access state in the target class or object through some methods that expose the state. Or a given trait might need some other functionality defined somewhere else. Those required methods are defined using a '@requires' annotation in the Trait or Talent definition.

Example

var cocktail = require('cocktail'),
    TraitA = require('TraitA'),
    MyTrait = function(){};


cocktail.mix(MyTrait, {
    '@requires': ['getData'],

    doSomethingWithData: function(){
        var data = this.getData(); //this method should be provided by the target class/object

        //do something with the data here
    }
}); 

module.exports = MyTrait;   

@exports: {Object} module object

Aplicable to Class, Trait, Talent, Object

Since v0.1.1

The @exports annotation is used to export the current mix as module.exports. The parameter should be the module variable where the current mix will be exported.

Example

var cocktail = require('cocktail'),
    MyClass = function(){};

cocktail.mix(MyClass, {
    '@exports': module,

    doSomethingWithData: function(){

        //do something with the data here
    }
});    

//this is not necessary anymore:
//module.exports = MyClass;

@as: {String} type to be created.

By now only 'class' is a valid parameter.

Aplicable to Class, Trait

Since v0.3.0

The @as pseudo-annotation is used to define the current mix as a class when using a Single parameter definition. A pseudo-annotation is used only on specific mixes, and it means it is an annotation without a processor. In this particular case, @as is only applicable when defining a mix with only one parameter.

Example

var cocktail = require('cocktail');

cocktail.mix({
    '@exports': module,
    '@as'     : 'class',

    doSomethingWithData: function(){
        //do something with the data here
    }
});    
comments powered by Disqus